zl程序教程

您现在的位置是:首页 >  其他

当前栏目

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

标准编程 阅读 专家 寻找 乐趣 1.9 ansi
2023-09-11 14:17:35 时间

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

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

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

1 foo(const char **p) { }

3 main(int argc, char **argv)

5 foo(arvg);

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

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++翻译器中是合法的(虽然这在将来可能会改变)。


ca9fe0e9513bf2ef9afdf0a21be44db98a331505

容易混淆的const

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

const可以用在数据上,如:

const int limit = 10;

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

const int * limitp = limit;

 int i = 27;

 limitp = 

这段代码表示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的指针的指针。)

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


2018-07-18 万马齐喑究可哀-中文编程的又一波 讨论 对问题 假设中国人最先开发电脑和设计程序语言,那么各种程序语言会使用汉字吗? 的回应. A response to the question what if the Chinese invented computer first?
文章最早发布于我的微信公众号 Android_De_Home 中,欢迎大家扫描下面二维码关注微信公众获取更多干货资源。
码栈开发手册(一)---编码方式开发(初级课程③) 开发手册的目的是帮助开发者快速的学会编写码栈应用。 目前提供了编写代码和可视化两种方式来开发。 编写代码适合稍微有一定的编程的同学,能够实现较为复杂的业务场景。 可视化适合没有任何编程基础的同学,能够实现较为简单的业务场景。
异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。