关于const的混淆
读《C专家编程》关注到的趣事
关于const的混淆
代码
1 | foo(const char **p) { } |
编译会报错:
1 | line 5: warning: argument is incompatible with protorype |
我在自己电脑上编译运行报错为:
1 | $ gcc consterr.c |
可以看到是类似的(passing argument 1 of ‘foo’ from incompatible pointer type [-Wincompatible-pointer-types])
也就是说,char **argv与const char **p并不相容。我尝试了其他测试用例:
对于整型是可以通过编译的(很显然)
1 | void foo(const int p) |
指向整型的指针也可以通过编译
1 | void foo(const int *p) |
接下来测试二级指针,果然和前面一样报错了:
1 | void foo(const int **p) |
1 | $ gcc consterr.c |
书中给出的ANSI C标准中描述:
每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)
说明传参过程类似于赋值,接下来我们可以使用赋值语句使问题的情境更清晰,并且从标准中对赋值操作的约束寻找线索。标准中关于赋值的描述有如下约束条件
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符
因此上述问题的产生来源于const究竟是哪个变量的限定符的混淆
测试以下代码是合法的
1 | char *cp; |
- 左操作数是一个指向有
const限定符的char类型的指针 - 右操作数是一个指向没有限定符的
char的指针
由于这两个指针都指向相同的类型char,自然是相容的,并且左边指针所指向的类型具有右边指针所指向类型的全部限定符,因此是合法的。
这个关于const的结论来自标准中
const float *类型并不是一个有限定符的类型——它的类型是”指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针本身。
回到最初的问题,原问题可以写作:
1 | char **cp2; |
左操作数ccp2是一个指向char类型的二级指针,其本身和指向的指针(假设为ccp)本身都没有限定符,而它指向的指针则指向一个含有const限定符的char类型
右操作数cp2是一个指向char类型的二级指针,其本身和指向的指针(假设为cp)本身都没有限定符,而它指向的指针则指向一个没有限定符的字符类型
由于ccp2和cp2指向的对象内容不同(尽管这两位都没有限定符),因此不能赋值。
尽管ccp和cp所指向的类型是相容的,但相容性并不是可以传递的,故不能表示ccp2和cp2所指向的类型也相容。
书中对标准的这一点是这么吐槽的(乐):
确实,整个标准好像由一位蹩脚的翻译把它从乌尔都语转译成丹麦语,再转译成英语而来,标准委员会似乎自我感觉良好,所以虽然人们希望语言的规则更简单一些、更清楚一些,但他们觉得这样做会破坏他们的良好感觉,所以拒不采纳。
作者补充道他修改了Sun的ANSI C编译器对此问题打印更多警告信息:
1 | Line 6: warning : argument #1 is imcompatible with prototype: |
对比了一下我的gcc输出的结果,好像还真差不多是这样,谢谢你泰罗。
(作者是Peter Van Der Linden)
另外作者表示const关键字或许命名为readonly更为合适,因为关键字const并不能把变量变成常量,只是表示其限定的符号不能被赋值,也就是说它的值对于这个符号来说是只读的,但它不能防止通过程序的内部(甚至是外部 )的方法来修改这个值。
所以const最常见的用处是限定函数的形参,这样该函数不会修改实参指针所指的数据,但其他函数可能会修改它(这就是标准库的代码中到处都是const的原因吗)。

