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

1.9 阅读ANSI C标准,寻找乐趣和裨益

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

1 foo(const char **p) { }
2
3 main(int argc, char **argv)
4 {
5          foo(arvg);
6 }

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

line 5: warning: argument is incompatible with prototype

(第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标准库中,所有的字符串处理函数就是这样的)。它之所以合法,是因为在下面的代码中:

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

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

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

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

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

容易混淆的const

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

const可以用在数据上,如:

const int limit = 10;

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

const int * limitp = &limit;
  int i = 27;
  limitp = &i;

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

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

类似地,你可以取一个const变量的地址,并且可以...(唔,我最好不要往大家的脑袋里灌输这种思想)。正如Ken Thompson所指出的那样,“const关键字可能引发一些罕见的错误,只会混淆函数库的接口。”回首往事,const关键字原先如果命名为readonly就好多了。
确实,整个标准好像是由一位蹩脚的翻译把它从乌尔都语转译成丹麦语,再转译成英语而来。标准委员会似乎自我感觉良好,所以虽然人们希望语言的规则更简单一些、更清楚一些,但他们觉得这样做会破坏他们的良好感觉,所以拒不采纳。

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

Line 6: warning : argument #1 is imcompatible with prototype:
  prototype: pointer to pointer to const char: "barf.c", line 1
  argument: pointer to pointer to char

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

原型:指向const char的指针的指针。"barf.c", 第1行
  实参:指向char的指针的指针。)

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

时间: 2024-10-26 12:29:34

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

《C专家编程》一导读

前 言 C专家编程 C代码.C代码运行.运行码运行-请! --Barbara Ling 所有的C程序都做同一件事,观察一个字符,然后啥也不干. --Peter Weinberger 你是否注意到市面上存有大量的C语言编程书籍,它们的书名具有一定的启示性,如:C Traps and Pitfalls(本书中文版<C陷阱与缺陷>已由人民邮电出版社出版), The C Puzzle Book, Obfuscated C and Other Mysteries,而其他的编程语言好像没有这类书.这里有一

由ANSI C标准咬文嚼字发现滴东东

  由ANSI C标准咬文嚼字发现滴东东   乱弹 奥运磨炼曲正酣,钢铁功夫解眼馋: 步履蹒跚遭埋怨:呆呆傻傻惹人烦: 浑浑噩噩心难安:四目相对眼茫然: 龙游浅水被虾戏:死不悔改表乱弹!                           (表:biao第四声)             侯佩|hopy    2008年05月08日于合肥电心         忙来有事,开始重读<< C专家编程 >>.     在第1章的1.9节[阅读ANSI C标准,寻找乐趣和裨益](第19页)中找

《C专家编程》一1.5 今日之ANSI C

1.5 今日之ANSI C 到了20世纪80年代初,C语言被业界广泛使用,但存在许多不同的实现和差别.PC的实现者发现了C语言优于BASIC的诸多长处,这一发现更是掀起了C语言的高潮.Mirosoft为IBM PC制作了一个C编译器,引入了几个新的关键字(far, near等)帮助指针处理Intel 80x86芯片不规则的架构.随着其他更多并非基于pcc的编译器的兴起,C语言受到了重复BASIC老路的威胁,也就是可能变成一种多个变种松散相关的语言. 形势渐渐明了,一个正式的语言标准是必需的.幸运

《C专家编程》一1.8 ANSI C标准的结构

1.8 ANSI C标准的结构 如果我们岔开话题,快速浏览一下ANSI C标准的出处和内容,对读者应该是有帮助的.ANSI C标准分成四个主要的部分: 第4节:介绍(共5页).对术语进行介绍和定义. 第5节:环境(共13页).描述了围绕和支持C语言的系统,包括在程序启动时发生什么,程序中止时发生什么,以及一些信号和浮点数运算.编译器的最低限制和字符集信息也在这一部分介绍. 第6节:C语言(共78页).标准的这部分是基于Dennis Ritchie数次出版的经典之作"The C Reference

《C专家编程》一1.11 轻松一下——由编译器定义的Pragmas效果

1.11 轻松一下--由编译器定义的Pragmas效果 自由软件基金会(Free Software Foundation)是一个独特的组织,它由MIT顶级黑客Richard Stallman所创立.顺便提一下,我们所说的"黑客",它的原先意思是"天才程序员".后来这个称呼被媒体所贬损,致使它在局外人眼中成了"邪恶的天才"的代名词.和形容词"bad"一样,"黑客"现在也有两个相反的意思,必须通过上下文才能明白

《C专家编程》一1.6 它很棒,但它符合标准吗

1.6 它很棒,但它符合标准吗 不要添乱--立即解散ISO工作小组. --匿名人士 ANSI C标准可以说是非常独特的,我们可以从好几个有趣的方面来说明这一点.它定义了下面一些术语,用于描述某种编译器的特点.如果你对这些术语有一个比较好的了解,就有助于你理解什么东西能被语言接受,什么东西不能被语言接受.前两个术语涉及不可移植的代码(unportable code),接下来的两个术语跟坏代码(bad code)有关,而最后两个术语则跟可移植的代码(portable code)有关. 不可移植的代码

《JavaScript专家编程》——9.4 度量JavaScript代码质量

9.4 度量JavaScript代码质量 为了让计算精度上升到最高,客观质量分析以程序化的方式对代码进行分析.这项任务可以使用编程工具完成,这些工具能够在多种情况下评估代码,根据各项指标得到最终的质量得分.本节介绍了静态代码分析,这种方法非常适合评估JavaScript的质量. 静态代码分析 静态代码分析就是不通过运行代码来分析代码的过程.静态分析看起来非常像一个文本编辑器的拼写检查器.拼写检查器扫描文档的正文来寻找错误和含糊之处,而并不需要了解文本的意义.同时,静态代码分析从功能上分析源代码的

《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理     使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是指UTF-16.也就是说每个字符编码为两个字节.65535个字符可以表示世界上大部分的语言.为了软件使国际化大家再编程时应该使用unicode字符集.由于原来学过c语言,不习惯

C专家编程 笔记

C语言中的符号重载 C语言非常的简洁, 以至于不愿意用太多的符号, 这样有很多符号在不同的地方有不同的含义 这样会让用户很困惑, 这是c的语言特性, 也是设计上的一些失误 static     在函数内部,表示该变量的值在各个调用间一直保持延续性: 对于函数,表示该函数只在本文件中可见 extern  用于变量,表示该变量在其它地方定义: 用于函数定义, 表示全局可见(属于冗余的) void      用于参数列表中,表示该函数参数为空,如int main(void): 用于返回值,表示该函数返