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
承诺不修改任何char
的cpp
可能被赋予指向指向非限定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 *x
和const 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 X
为pointer to const X
任何类型X
pointer to const X
的pointer 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)"
您只能将限定符(即const
, volatile
和restrict
限定符)添加到第一级指针。 你不能将它们添加到更高级别的指针,因为正如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是正确的。