Strange warning in argument of multidimensional array of const function

I get some strange warnings about this code:

typedef double mat4[4][4]; void mprod4(mat4 r, const mat4 a, const mat4 b) { /* yes, function is empty */ } int main() { mat4 mr, ma, mb; mprod4(mr, ma, mb); } 

gcc output as follows:

 $ gcc -o test test.c test.c: In function 'main': test.c:13: warning: passing argument 2 of 'mprod4' from incompatible pointer type test.c:4: note: expected 'const double (*)[4]' but argument is of type 'double (*)[4]' test.c:13: warning: passing argument 3 of 'mprod4' from incompatible pointer type test.c:4: note: expected 'const double (*)[4]' but argument is of type 'double (*)[4]' 

If I define the function as:

 void mprod4(mat4 r, mat4 a, mat4 b) { } 

Or defining matrices mainly like:

 mat4 mr; const mat4 ma; const mat4 mb; 

Or call the main function as follows:

 mprod4(mr, (const double(*)[4])ma, (const double(*)[4])mb); 

Or even define mat4 as:

 typedef double mat4[16]; 

Gets a warning. What's going on here? Am I doing something invalid?

The gcc version is 4.4.3 if necessary.

I also posted on gcc bugzilla: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47143

My current workaround is making ugly macros that throw things for me:

 #ifndef _NO_UGLY_MATRIX_MACROS #define mprod4(r, a, b) mprod4(r, (const double(*)[4])a, (const double(*)[4])b) #endif 

Reply from Joseph S. Myers on gcc bugzilla:

Not a mistake. The parameters of the function are of type "pointer to an array [4] of const double", because const on the array type is applied to the element type, recursively, and then the outermost type of the array, only the type parameter splits into a pointer and the arguments passed to the type pointer to array [4] of double "after the matrix decays into a pointer, and the only case when qualifiers are allowed to be added to a task, passing arguments, etc. are qualifiers for the immediate purpose of the pointer, not nested deeper.

This sounds pretty confusing to me as a function expects:

 pointer to array[4] of const doubles 

and we pass

 pointer to const array[4] of doubles 

Intead

Or will it be the opposite? Warnings suggest that the function expects:

 const double (*)[4] 

which seems to me more like

 pointer to const array[4] of doubles 

I am really confused by this answer. Can anyone who understands what he said clarifies and illustrates?

+6
c function multidimensional-array arguments const
source share
6 answers

I believe that the problem is related to the restrictions specified in C99 6.5.16.1 (1) , which appear to prohibit the confusion of qualifications in appointments, with the exception of pointers for which the exclusion of an inclusive classifier is defined. The problem is that with indirect pointers, you pass a pointer to one thing to a pointer to another. The assignment is invalid because if it were, you could trick it into modifying the object with the content using the following code:

 const char **cpp; char *p; const char c = 'A'; cpp = &p; // constraint violation *cpp = &c; // valid *p = 0; // valid by itself, but would clobber c 

It might seem reasonable that cpp , to which promises do not change any char s, may be assigned a pointer to an object that points to unqualified char s. In the end, it allowed one-way pointers, so for example, you can pass the mutable object to the second strcpy(3) parameter, the first strchr(3) parameter, and many other parameters declared with const .

But with an indirect pointer at the next level, assignment with the help of a qualified pointer is allowed, and now a completely unconditional assignment of a pointer will compress the qualified object.

I don’t immediately see how a 2-D array can lead to this situation, but in any case it faces the same limitation in the standard.

Since in your case you are not actually cheating on it in clobbering const, the right thing for your code would seem to be inserting a listing.


Update: OK, as this happens, this problem is in C faq , and this whole discussion also went through several times on the gcc error list and on the gcc mailing list.

Lesson: you can pass T *x when const T *x is expected with an explicit exception, but T *x and const T *x are still different types, so you cannot pass a pointer to one to one pointer to another.

+12
source share

To explain what Joseph said: the function expects a pointer to array[4] of const double , but you pass in pointer to array[4] of double . These types are incompatible, so you get an error. They look as if they should be compatible, but it is not.

To pass parameters to functions (or to assign variables) you can always convert X to const X or pointer to X to pointer to const X for any type of X For example:

 int x1 = 0; const int x2 = x1; // ok int *x3 = &x1; const int *x4 = x3; // ok: convert "pointer to int" to "pointer to const int" int **x5 = &x3; const int **x6 = x5; // ERROR: see DigitalRoss answer int *const *x7 = x5; // ok: convert "pointer to (pointer to int)" to // "pointer to const (pointer to int)" 

You are allowed to add qualifiers (that is, const , volatile and restrict qualifiers) to the first level of pointers. You cannot add them to higher levels of pointers because, as DigitalRoss noted, this will allow you to accidentally violate const-correctness. This is what Joseph means "the only case where qualifiers can be added to assignment, passing arguments, etc. qualifiers to the nearest pointer target, rather than those that are nested deeper."

So, returning us to Joseph’s answer, you cannot convert pointer to array[4] of double to pointer to array[4] of const double , because there is no type X that you convert from pointer to X to pointer to const X

If you try to use array[4] of double for X , you will see that you can convert to pointer to const array[4] of double , which is a different type. However, there is no such type in C: you can have an array of type const , but there is no such thing as a const array .

Therefore, there is no way to solve your problem perfectly. You need to either add throws to all calls to your function (either manually, or through a macro or helper function), rewrite your functions so as not to accept const parameters (bad, since it does not allow you to pass const matrices), or change mat4 type as one array or structure, as user502515 suggested .

+5
source share

Here's the problem (IMHO): double[4][4] in the function signature.

You know this as double[4][4] , but the compiler sees double(*)[4] in the function parameter list, which, in particular, does not have an array size limit. It turns your 2D array of 4 objects into a pointer to a 1D array of 4 objects, and the pointer can be properly indexed, as if it was an array of 4 objects.

I would pass all mat4 objects with a pointer:

 void mprod4(mat4 *r, const mat4 *a, const mat4 *b); // and, if you don't want to hairy your syntax #define mprod4(r, a, b) (mprod4)(&r, (const mat4 *)&a, (const mat4 *)&b) 

This will (I believe) guarantee the correctness and correctness of the size of the array. This can make mprod4 little harder to write and still contain some hairy videos, but it will (IMHO) be worth it (especially after the macro above):

 void mprod4(mat4 *r, const mat4 *a, const mat4 *b) { // all indexing of the matricies must be done after dereference for(int i = 0; i < 4; i++) for(int j = 0; j < 4; j++) { (*r)[i][j] = (*a)[i][j] * (*b)[i][j]; // you could make it easier on yourself if you like: #define idx(a, i, j) ((*a)[i][j]) idx(r, i, j) = idx(a, i, j) * idx(b, i, j) } } 

It may look a little bad when you write it, but I think it will be cleaner in type. (Maybe I was thinking too much about C ++ ...)

+1
source share

I think you can do this in C99, but I'm not sure if this will help:

 void mprod4(double mr[4][4], double ma[const 4][const 4], double mb[const 4][const 4]) { } 

I don't have a C99 compiler, but I remember reading something in the C99 specification regarding qualifiers in array [] for arrays as arguments. You can also put static there (for example, ma[static 4] ), but of course that means something else.

Edit

Here it is, section 6.7.3.5, paragraph 7.

Declaring a parameter as a "type array" should be adjusted to a "qualified type pointer", where type qualifiers (if any) are those specified in [ and ] to output the array type, If the static also appears inside [ and ] to output an array type, then for each function call, the value of the corresponding actual argument should provide access to the first element of the array using the least number of elements indicated by the size expression.

+1
source share

To practically solve this problem, one could use a structure, and changing double[4][4] to a-bit-awkward double (*)[4] can be avoided, and the constant also works intuitively - using the same amount of memory:

 struct mat4 {
         double m [4] [4];
 };

 void myfunc (struct mat4 * r, const struct mat4 * a, const struct mat4 * b)
 {
 }

 int main (void)
 {
         struct mat4 mr, ma, mb;
         myfunc (& mr, & ma, & mb);
 }
+1
source share

The compiler is just anal.

You pass an argument, which is essentially a non-constant pointer, and the function is declared as taking a const pointer as an argument. These two are essentially incompatible. This is not a problem, because the compiler should still work as long as you can assign the value of the first type to a variable of the second type. Therefore, a warning, but not an error.

EDIT: It looks like gcc is not complaining about other conversions from const-const to const, for example. passing char * where const char * is expected. In this case, I tend to agree that Joseph Myers of the Bugzilla is faithful.

-one
source share

All Articles