4

I want to write a C function that takes a dynamic 2D array as an input, but doesn't alter the array.

I'm trying to be const correct, not only to make my code clearer, but because my functions are going to be called from within C++ code, and C++ is pretty persnickety about these things.

How do I declare a function to take 'const' pointer to a pointer, i.e. how do I indicate that the function will not alter the contents of the 2d array?

What follows is a specific, super-simple example. I'm using a 2D array of doubles, i.e. double**, to represent a square matrix in C of size n x n, and I want to write a function that computes the trace of one of these matrices:

#include <stdlib.h>
#include <stdio.h>

double **sqr_matrix_new(int n)
{
  double **a = calloc(n, sizeof(double*));
  int i;
  for (i=0; i < n; ++i) a[i] = calloc(n, sizeof(double));
  return a;
}

void sqr_matrix_free(double **a, int n)
{
  int i;
  for (i=0; i < n; ++i) free(a[i]);
  free(a);
}

double sqr_matrix_trace(double **a, int n)
{
  double trace;
  int i;
  for (i=0, trace=0.0; i < n; ++i) trace += a[i][i];
  return trace;
}

double sqr_matrix_trace_const(const double * const *a, int n)
{
  double trace;
  int i;
  for (i=0, trace=0.0; i < n; ++i) trace += a[i][i];
  return trace;
}

int main(int argc, char *argv[])
{
  int n = 10;
  double **a = sqr_matrix_new(n);
  int i, j, k;
  for (i=0, k=0; i < n; ++i){
    for (j=0; j < n; ++j) a[i][j] = k++;
  }
  printf("trace is %g\n", sqr_matrix_trace(a, n));
  printf("trace is %g\n", sqr_matrix_trace_const(a, n));
  printf("trace is %g\n", sqr_matrix_trace_const((const double * const *)a, n));
  sqr_matrix_free(a, n);
}

In the above, both versions of the trace function, sqr_matrix_trace() and sqr_matrix_trace_const() compile cleanly (the latter is the one I prefer because it clearly demonstrates that there will be no alteration of the matrix it's given), but the call

sqr_matrix_trace_const(a, n)

produces the following warning:

sqr_matrix.c: In function 'main':
sqr_matrix.c:44: warning: passing argument 1 of 'sqr_matrix_trace_const' from incompatible pointer type
sqr_matrix.c:27: note: expected 'const double * const*' but argument is of type 'double **'

The cast overcomes this:

sqr_matrix_trace_const((const double * const *)a, n)

but it feels wrong to use a cast to use to overcome compiler inconveniences.

Alternatively, I could suppress the compiler warning, but that's a cop-out.

So, I want my code to compile cleanly and I want to convey the const-ness of a dynamic 2D array given to a function without resorting to a cast. It seems like a legitimate aim. Is this possible? If not, what's the standard/accepted practice for doing this?

user1453344
  • 41
  • 1
  • 3
  • Duplicate: http://stackoverflow.com/questions/4573349/c-function-const-multidimensional-array-argument-strange-warning – math Jun 13 '12 at 12:36

3 Answers3

3

The C const promotion rules don't allow promotion from T ** to const T const *. Per 6.5.16.1 1 (which applies to function calls as well as assignments per 6.5.2.2 2), conversion of pointers can only add qualifiers to the pointed-to type.

This is to prevent code like (example from 6.5.16.1 6):

const char **cpp;
char *p;
const char c = 'A';
cpp = &p; // constraint violation
*cpp = &c; // valid
*p = 0; // valid

It's correct to observe that const *const *cpp = &p is safe because then *cpp = &c is prevented, but this is a sufficiently obscure case that it's not covered in the standard.

Conclusion: you can and should cast to const double *const * yourself.

Note that it would be more efficient to use a single array of type double * with length n * n and do any necessary array indexing yourself: d[i][j] becomes d[i * n + j].

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • I think that comment is intended for Milan's answer http://stackoverflow.com/a/11014916/567292 ? – ecatmur Jun 13 '12 at 15:40
  • The `double**` to `double*` trick tries to allocate a single memory block for the array, is that correct? If so, it may improve performance (fewer CPU cache misses) but on a memory constrained environment (eg. embedded stuff), the single contiguous memory block may not be available. – felipeduque Mar 15 '19 at 13:27
1

A C++ compiler would allow that.

As for C, qualified pointer types are not applied recursively.

Milan
  • 15,389
  • 20
  • 57
  • 65
0

If your matrix data truly is 2D and rectangular (without a "ragged right edge"), I don't see why you're not representing it as a single double * to the first element, together with integers giving width and height. This would allow you to both cut down on the number of allocations needed to initialize the matrix, but also make it representable as a plain old const double *.

unwind
  • 391,730
  • 64
  • 469
  • 606