C函数const多维数组参数中的奇怪警告

我对这段代码有一些奇怪的警告:

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输出如下:

 $ 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]' 

如果我将函数定义为:

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

或者在main中定义矩阵:

 mat4 mr; const mat4 ma; const mat4 mb; 

或者在main中调用函数:

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

甚至将mat4定义为:

 typedef double mat4[16]; 

使警告消失。 这里发生了什么? 我做的事情无效吗?

如果相关,gcc版本是4.4.3。

我还发布了gcc bugzilla: http ://gcc.gnu.org/bugzilla/show_bug.cgi?id = 47143

我目前的解决方法是为我制作丑陋的宏:

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

Joseph S. Myers对gcc bugzilla的回答:

不是错误。 函数参数的类型是“指向const double的数组[4]”,因为数组类型的const递归地应用于元素类型,然后只有数组类型的参数的最外层数组类型衰减到指针,并且在数组到指针衰减之后传递的参数是“指向数组[4]的指针”类型,并且唯一允许在赋值,参数传递等中添加限定符的情况是立即指针上的限定符目标,而不是那些更深层次的嵌套。

对我来说听起来很混乱,就像函数所期望的那样:

 pointer to array[4] of const doubles 

我们正在过世

 pointer to const array[4] of doubles 

这一翻译。

或者它会反过来? 警告表明该function需要:

 const double (*)[4] 

在我看来更像是一个

 pointer to const array[4] of doubles 

我真的很困惑这个答案。 有人明白他说的话会澄清并举例说明吗?

我认为问题是C99 6.5.16.1(1)中规定的约束,它似乎禁止在赋值中混合限定条件,除了定义了包含限定符exception的指针。 问题是,使用间接指针,最终会将指向一个事物的指针传递给指向另一个事物的指针。 赋值无效,因为如果是,您可以使用以下代码来修改const限定对象:

 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 

承诺不修改任何charcpp可能被赋予指向指向非限定char的对象的指针似乎是合理的。 毕竟,这是允许单间接指针,这就是为什么,例如,你可以将一个可变对象传递给strcpy(3)的第二个参数, strchr(3)的第一个参数,以及许多其他参数声明const

但是使用间接指针,在下一级别,允许从限定指针进行赋值,现在一个完全不合格的指针赋值将破坏一个限定对象。

我没有立即看到2-Darrays如何导致这种情况,但无论如何它都会遇到标准中的相同约束。

因为在你的情况下,你实际上并没有欺骗它来破坏const, 你的代码的正确之处似乎是插入强制转换。


更新:好的家伙,因为它发生了这个问题在C faq中 ,整个讨论也在gcc错误列表和gcc邮件列表上多次发生。

  • Gcc错误列表: http ://gcc.gnu.org/bugzilla/show_bug.cgi?id = 20230。
  • C FAQ:问题11.10: http//c-faq.com/ansi/constmismatch.html

经验教训:当需要const T *x T *x时,你可以通过显式exception传递一个T *x但是T *xconst T *x仍然是不同的类型,所以你不能将一个指针传递给一个指针到另一个。

为了解释Joseph所说的:该函数期望传入一个pointer to array[4] of double pointer to array[4] of const double ,但是你传入一个pointer to array[4] of double 。 这些类型不兼容,因此您会收到错误。 他们看起来应该兼容,但他们不是。

为了将参数传递给函数(或用于变量赋值),您总是可以将X转换为const X ,或者将pointer to Xpointer to const X任何类型X pointer to const Xpointer to const X 。 例如:

 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's answer int *const *x7 = x5; // ok: convert "pointer to (pointer to int)" to // "pointer to const (pointer to int)" 

您只能将限定符(即constvolatilerestrict限定符)添加到第一级指针。 你不能将它们添加到更高级别的指针,因为正如DigitalRoss所提到的那样 ,这样做会让你意外地违反const-correctness。 这就是Joseph所说的“唯一允许在赋值中添加限定符的情况,参数传递等是直接指针目标上的限定符,而不是那些嵌套得更深的东西。”

因此,让我们回到Joseph的响应,你不能将pointer to array[4] of double转换为pointer to array[4] of double pointer to array[4] of const double因为没有类型X这样你就可以从pointer to X转换pointer to X pointer to const X

如果你尝试对X使用array[4] of double ,你会看到你可以转换为pointer to const array[4] of double ,这是一个不同的类型。 但是,C中不存在这样的类型:你可以有一个const类型的数组,但是没有const array这样的东西。

因此,没有办法完美地解决您的问题。 您必须向所有函数调用添加强制转换(手动或通过宏或辅助函数),重写函数以不接受const参数(因为它不允许您传入const矩阵,所以不好),或者将mat4类型更改为1维数组或结构,如user502515建议的那样 。

为了实际解决这个问题,可以使用一个结构,并且避免将double[4][4]更改为a-bit-awkward double (*)[4] ,并且constness也可以直观地工作 – 而相同数量的使用内存:

 struct mat4 {
        双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);
 }

我想在C99中你可以做到这一点,但我不确定它会有所帮助:

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

我还没有得到C99编译器,但我记得在C99规范中读到了关于数组中[]限定符作为参数的内容。 你也可以把static放在那里(例如ma[static 4] )但当然这意味着别的东西。

编辑

这是第6.7.3.5节第7段。

参数声明为“数组类型”应调整为“限定指向类型的指针”,其中类型限定符(如果有)是在数组类型派生的[]内指定的那些。 如果关键字static也出现在数组类型派生的[]中,那么对于每次对函数的调用,相应的实际参数的值应该提供对数组的第一个元素的访问,其中元素的数量至少与指定的元素一样多。按大小表达。

这是一个问题(恕我直言):在函数签名中double[4][4]

知道它是double[4][4] ,但编译器在函数参数列表中看到double(*)[4] ,它特别没有数组大小约束。 它将4 x 4对象的2D数组转换为指向4个对象的1D数组的指针,并且指针可以被有效地索引,就像它是4个对象的数组一样。

我会通过指针传递所有mat4对象:

 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) 

这将(我相信)确保const正确性数组大小的正确性。 它可能会使mprod4更难写,并且仍然涉及一些毛茸茸的演员表,但它(恕我直言)值得(特别是在上面的宏之后):

 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) } } 

当你写它时看起来有点不好,但我认为它在类型方面会更清晰。 (也许我一直在想C ++太多......)

编译器只是肛门。

您传递的参数本质上是一个非const指针,并且该函数被声明为接受一个const指针作为参数。 事实上,这两者是不相容的。 这不是一个真正的问题,因为只要您可以将第一种类型的值分配给第二种类型的变量,编译器仍然可以工作。 因此警告但不是错误。

编辑:看起来像gcc没有抱怨其他con-const到const转换,例如传递char *,其中const char *是预期的。 在这种情况下,我倾向于同意Bugzilla的Joseph Myers是正确的。