天天看点

《C专家编程》一1.9 阅读ANSI C标准,寻找乐趣和裨益

本节书摘来自异步社区《c专家编程》一书中的第1章,第1.9节,作者 【美】perter van der linde,更多章节内容可以访问云栖社区“异步社区”公众号查看

有时候必须非常专注地阅读ansi c标准才能找到某个问题的答案。一位销售工程师把下面这段代码作为测试例发给sun的编译器小组。

如果编译这段代码,编译器会发出一条警告信息:

(第5行:警告:参数与原型不匹配)。

提交代码的工程师想知道为什么会产生这条警告信息,也想知道ansi c标准的哪一部分讲述了这方面的内容。他认为,实参char s与形参const char p应该是相容的,标准库中所有的字符串处理函数都是这样的。那么,为什么实参char argv与形参const char p实际上不能相容呢?

答案是肯定的,它们并不相容。要回答这个问题颇费心机,如果研究一下获得这个答案的整个过程,会比仅仅知道结论更有意义。对这个问题的分析是由sun的其中一位“语言律师”[6]进行的,其过程如下:

在ansi c标准第6.3.2.2节中讲述约束条件的小节中有这么一句话:

每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)。

这就是说参数传递过程类似于赋值。

所以,除非一个类型为char 的值可以赋值给一个const char 类型的对象,否则肯定会产生一条诊断信息。要想知道这个赋值是否合法,就请回顾标准中有关简单赋值的部分,它位于第6.3.16.1节,描述了下列约束条件:

要使上述的赋值形式合法,必须满足下列条件之一:

两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。

正是这个条件,使得函数调用中实参char能够与形参const char匹配(在c标准库中,所有的字符串处理函数就是这样的)。它之所以合法,是因为在下面的代码中:

左操作数是一个指向有const限定符的char的指针。

右操作数是一个指向没有限定符的char的指针。

char类型与char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符(const)。

注意,反过来就不能进行赋值。如果不信,试试下面的代码:

标准第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节所规定的约束条件,编译器必然会产生一条诊断信息。

用这种方式理解这个要点有一定困难。可以用下面这个方法进行理解:

左操作数的类型是foo2,它是一个指向foo的指针,而foo是一个没有限定符的指针,它指向一个带有const限定符的char类型,而且……

右操作数的类型是baz2,它是一个指向baz的指针,而baz是一个没有限定符的指针,它指向一个没有限定符的字符类型。

foo和baz所指向的类型是相容的,而且它们本身都没有限定符,所以符合标准的约束条件,两者之间进行赋值是合法的。但foo2和baz2之间的关系又有不同,由于相容性是不能传递的,foo和baz所指向的类型相容并不表示foo2和baz2所指向的类型也相容,所以虽然foo2和baz2都没有限定符,但它们之间不能进行赋值。也就是说,它们都是不带限定符的指针,但它们所指向的对象是不同的,所以它们之间不能进行赋值,也就不能分别作为函数的形参和实参。但是,这个约束条件很令人恼火,也很容易让用户混淆。所以,这种赋值方法目前在基于cfront的c++翻译器中是合法的(虽然这在将来可能会改变)。

《C专家编程》一1.9 阅读ANSI C标准,寻找乐趣和裨益

容易混淆的const

关键字const并不能把变量变成常量!在一个符号前加上const限定符只是表示这个符号不能被赋值。也就是它的值对于这个符号来说是只读的,但它并不能防止通过程序的内部(甚至是外部)的方法来修改这个值。const最有用之处就是用它来限定函数的形参,这样该函数将不会修改实参指针所指的数据,但其他的函数却可能会修改它。这也许就是c和c++中const最一般的用法。

const可以用在数据上,如:

这和其他语言差不多,但当你在等式两边加上指针,就有一定难度了:

这段代码表示limitp是一个指向常量整型的指针。这个指针不能用于修改这个整型数,但是在任何时候,这个指针本身的值却可以改变。这样,它就指向了不同的地址,对它进行解除引用(dereference)操作时会得到一个不同的值!

const和的组合通常只用于在数组形式的参数中模拟传值调用。它声称“我给你一个指向它的指针,但你不能修改它。”这个约定类似于极为常见的void 的用法,尽管在理论上它可以用于任何情形,但通常被限制于把指针从一种类型转换为另一种类型。

类似地,你可以取一个const变量的地址,并且可以...(唔,我最好不要往大家的脑袋里灌输这种思想)。正如ken thompson所指出的那样,“const关键字可能引发一些罕见的错误,只会混淆函数库的接口。”回首往事,const关键字原先如果命名为readonly就好多了。

确实,整个标准好像是由一位蹩脚的翻译把它从乌尔都语转译成丹麦语,再转译成英语而来。标准委员会似乎自我感觉良好,所以虽然人们希望语言的规则更简单一些、更清楚一些,但他们觉得这样做会破坏他们的良好感觉,所以拒不采纳。

我感觉,将来还会有许多人产生类似的疑问,而且并不是他们中的每一个人都会仔细揣摩前面详述的推理过程。所以,我们修改了sun的ansi c编译器,当它发现不相容的情况时,会打印出更多的警告信息。原先那个例子将会产生的完整信息如下:

(第6行:警告:#1实参与原型不相容:

即使程序员不明白为什么会这样,他至少应该明白什么是不相容。

继续阅读