1

I've written the following function in C to print out a dynamically created matrix but guarantee that inside the function I

  1. Can't modify the pointer I'm given
  2. Can't modify any of the interior pointers
  3. Can't modify any of the numbers in the matrix

 void const2dPrinter(const int *const *const mat, const int numRows, const int numCols){
  for(int i =0; i < numRows; ++i){
    for(int j =0; j < numCols; ++j){
      printf("%d ", mat[i][j]);
    }
    printf("\n");
  }
 }

I attempt to call the function in main as follows

int main(){
  int numRows = 3;
  int numCols = 4;
  int** mat = (int**) malloc(numRows * sizeof(int*);
  for(int i = 0; i < numRows; ++i){
    mat[i] = (int*)malloc(numCols * sizeof(int));
    for(int j = 0; j < numCols; ++j){
      mat[i][j] = (i +1) * (j+1);
    }
  }
  const2dPrinter(mat, numRows, numCols);
...
}

But I get the following error from the compiler

error: passing argument 1 of 'const2dPrinter' from incompatible pointer type [-Werror=incompatible-pointer-types]
   const2dPrinter(mat, numRows, numCols);
note: expected 'const int * const* const' but argument is of type 'int **'
   void const2dPrinter(const int *const *const const mat, const int numRows, const int numCols)

My IDE gives the following error:

Parameter type mismatch: Assigning int** to const int* const* const* discards const qualifier. 

The code will work if I remove the first const in the declararation of the function. Going from void const2dPrinter(const int *const *const mat, ...){

to void const2dPrinter(int *const *const mat, ...){

but then I'm able to modify the values of the matrix in const2dPrinter, which I don't want to be able to do.

I know that I can cast the int** to a const int *const *const when calling the function to get things to work as shown below, but I'm incredibly confused as to why what I have right now doesn't work as everything I have read says that you should be able to have a const pointer point at at a non const value.

const2dPrinter((const int* const* const) mat, numRows, numCols); //this works 

I've tried searching for a solution but haven't been able to find one. The code will compile on a C++ compiler without issue but won't work on my C compiler (gcc).

Any advice on how to fix my problem as well as an explanation of why what I have now doesn't work would be greatly appreciated.

mfbutner
  • 141
  • 6

2 Answers2

2

You have to write the ugly cast (although the last const is redundant). It is an oversight in the C specification that there is no implicit conversion to change T ** to T const * const *.

I think in practice people tend to end up just not using const in situations where there will be pointers-to-pointers, or pointers-to-arrays, because of this problem. You can work around it with casts, and/or a generic selector etc. but things quickly seem to get complicated to support what should be a simple operation.


For your specific situation another approach might be to use a single-dimension array to store the matrix. (This is probably going to be more efficient at runtime due to requiring a smaller number of allocations).

M.M
  • 138,810
  • 21
  • 208
  • 365
1

In order to pass the parameter as a constant pointer to constant int you want your parameter to be:

void const2dprinter (const int* const* mat, const int numrows, const int numcols)

There is no need to cast the return of malloc, it is unnecessary. See: Do I cast the result of malloc?. Your allocations should also use the dereferenced pointer to insure you always obtain the proper type-size, e.g.

    int **mat = malloc (numrows * sizeof *mat);
    ...
        mat[i] = malloc (numcols * sizeof *mat[i]);

Build good habits now. If you allocate memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. Keep track of what you allocate, and then free the memory before losing the pointer. A simple free function is all that is required, e.g.

void mat_free (int **mat, const int numrows)
{
    for (int i = 0; i < numrows; i++)
        free (mat[i]);      /* free rows */
    free (mat);             /* free pointers */
}

Putting it altogether, you could do something similar to the following:

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

void const2dprinter (const int* const* mat, const int numrows, const int numcols)
{
    for (int i =0; i < numrows; ++i){
        for(int j =0; j < numcols; ++j)
            printf("%d ", mat[i][j]);
        printf("\n");
    }
}

void mat_free (int **mat, const int numrows)
{
    for (int i = 0; i < numrows; i++)
        free (mat[i]);      /* free rows */
    free (mat);             /* free pointers */
}

int main (void) {

    int numrows = 3;
    int numcols = 4;
    int **mat = malloc (numrows * sizeof *mat);

    for (int i = 0; i < numrows; ++i) {
        mat[i] = malloc (numcols * sizeof *mat[i]);
        for (int j = 0; j < numcols; ++j)
            mat[i][j] = (i +1) * (j+1);
    }
    const2dprinter ((int const * const *)mat, numrows, numcols);
    mat_free (mat, numrows);
}

Example Use/Output

$. /bin/mat_const_int_2d
1 2 3 4
2 4 6 8
3 6 9 12

Look things over and let me know if you have questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thanks for the tip about using sizeof on the derefenced pointer to ensure that I always allocate the correct amount of space. In your answer it looks like you used a cast in the call and based on @M.M.'s answer it seems like it is a necessity. The cast was what I was hoping to avoid but it doesn't sound like there is a way around it :( – mfbutner Jul 20 '18 at 04:02
  • No, there is no way around the cast to compile without a warning (which all your code should compile cleanly without warning). Without the cast, you receive the warning `"expected ‘const int * const*’ but argument is of type ‘int **’"`. (it is a warning -- the code will work, but do not accept code until it compiles *without* warnings) On gcc/clang always compile with `-Wall -Wextra -pedantic` (minimum) or for VS (`cl.exe`) use `/W3` (minimum) or you can use `/W4` -- but you will generate a number of non-code related warning. – David C. Rankin Jul 20 '18 at 04:25