有时候必须非常专注地阅读ANSI C标准才能找到某个问题的答案。一位销售一程师把下面这段代码作为测试例发给Sun的编译器小组。
foo(const char **p){}
main(int argc,char **argv)
{
foo(argv);
}
如果编译这段代码,编译器会发出一条警告信息:
line5:warning:argument is incompatible with prototype
(第5行:警告:参数与原型不匹配)
提交代码的工程师想知道为什么会产生这条警告信息,也想知道ANSI C标准的哪一部分讲述了这方面的内容。他认为,实参char* s与形参const char* p应该是相容的,标准库中所有的字符串处理函数都是这样的。那么,为什么实参char **argv与形参const char **p实际上不能相容呢?
答案是肯定的,它们并不相容。要回答这个问题颇费心机,如果研究一下获得这个答案的整个过程,会比仅仅知道结论更有意义。对这个问题的分析是由Sun的其中一位“语言律师”进行的,其过程如下:
在ANSI C标准第6.3.2.2节中讲述约束条件的小节中有这么一话:
每个实参都应该具有自己的类型,这样它的值就可以赋给与它所对应的形参类型的对象(该对象的类型不能含有限定符)。
这就是说参数传递过程类似于赋值。
所以,除非一个const char **类型的对象可以赋值给一个类型为char **的值,否则肯定会产生一条诊断信息。要想知道这个赋值是否合法,就请回顾标准中有关简单赋值的部分,它位于第6.3.16.1节,描述了下列约束条件:
要使上述的赋值形式合法,必须满足下列条件之一:
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
正是这个条件,使得函数调用中实参char *能够与形参const char *匹配(在C标准库中,所有的字符串处理函数就是这样的)。它之所以合法,是因为在下面的代码中:
char *cp;
const char *ccp;
ccp=cp;
左操作数是一个指向有const限定符的char的指针。
右操作数是一个指向没有限定符的char的指针。
char类型与char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符(const)。
注意,反过来就不能进行赋值。如果不信,试试下面的代码:
cp=ccp; /*结果产生编译警告*/
标准第6.3.16.1节有没有说char **实参与const char *形参是相容的?没有。
标准第6.1.2.5节中讲述实例的部分声称:
const float *类型并不是一个有限定符的类型——它的类型是“指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针本身。
类似地,const char **也是一个没有限定符的指针类型,它的类型是“指向有const限定符的char类型的指针的指针”。
由于char **和const char **都是没有限定符的指针类型,但它们所指向的类型不一样(前者指向char *,后者指向const char *),因此它们是不相容的。因此,类型为char **的实参与类型为const char **的形参是不相容的,违反了标准第6.3.2.2节所规定的约束条件,编译器必然会产生一条诊断信息。