1、出人意料的Warning
test_cpp.c
#include <stdlib.h>
#include <stdio.h>
int main()
{
char c = 'c';
char *p = &c;
char **pp = &p;
const char **cpp = &p;
char const **c_pp = &p;
char * const *p_const_p = &p;
char ** const ppc = &p;
return 0;
}
编译的时候有如下warning:
x@ubuntu:~/Desktop/const$ gcc text_cpp.c
text_cpp.c: In function ‘main’:
text_cpp.c:11:21: warning: initialization of ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types]
const char **cpp = &p;
^
text_cpp.c:12:22: warning: initialization of ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types]
char const **c_pp = &p;
^
看到上面的warning,第一时间就是有点懵,和"经验不符“,我们常见的是如下用法:
#include <stdlib.h>
#include <stdio.h>
int main()
{
char c = 'c';
const char *p = &c;
char const *q = &c;
char *const r = &c;
return 0;
}
上述用法都是ok的,编译不会有warning。
2、 解释
2.1 理论背景介绍
查了很多资料,大致搞清楚了上面warning的原因。上面warning原因在《C专家编程》中有解释,而且也参考了本文后面网址的资料。
先来看看ANSI C中的规定:“两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针指向的类型必须具有右边指针指向类型的全部限定符“。这句话的前半句强调类型,后半句强调限定符(修饰符)。 出现编译warning的地方,如果不看修饰符const,赋值完全没有问题,问题出现在const修饰符。还是上面那句话,需要进行两点说明: (1) 相容类型:在《ANSI C》的6.2.7节中,对相容类型(compatible type)的定义为:Two types have compatible type if their types are the same. (2) 指针指向类型:字面意思理解,应该指的是对指针仅进行一次解引用得到的类型。
2.2. 应用理论解释
根据上面的说明,分别对下图中的三种情况进行解释:
情况一:
char **pp = &p等式两边的两个操作数都没有限定符去,且pp指向的是char *类型,&p指向也是char *类型,是相容类型,符合前面那句话的前半句,两个操作数都是指向无限定符的相容类型指针,那么这个赋值ok,没有任何问题。
情况二
char * const *pp = &p。pp 指向的是char * 类型,&p指向是char *类型,等式两边的类型相容,但是等式左边pp指向的类型有const关键字修饰,右边操作数没有限定符。指向类型一个有限定符,一个没有限定符,不符合前面那句话的前半句:两个操作数都是指向有限定符或无限定符的相容类型的指针。
需要注意的是左边的const是pp指向类型*pp的限定符,指的是不能通过对pp一次解引用改变p的值,也就是说const是左边操作数指向类型的限定符。
接着看是否符合后半句,左边指针指向的类型有const限定符,右边指针指向的类型没有限定符,符合后半句:左边指针指向的类型必须具有右边指针指向类型的全部限定符。 可以这么理解左边指针指向的限定符集合是A={const, Ø}, 右边指针指向类型的限定符集合是B={Ø},很显然 A 具有B的所有限定符。
情况三
const char **pp = &p等式左边pp指向的类型是const char *,&p指向是char *类型,不是相容类型。这是因为pp指向的类型是 *pp(const char *), *pp (const char *)没有限定符,const 限定符修饰的不是*pp, 而是*pp指向的类型**p(char), 显然这是对pp解引用二次了;&p指向的类型是p(char *)。 核心问题要确定const限定符修饰的对象。