如何通过阅读C标准来解决C语言语法问题
有时候必须非常专注地阅读ANSI C标准才能找到某个问题的答案。一位销售工程师把下面这段代码作为测试用例发给Sun的编译小组。
foo(const char **p)
{} int main(int argc, char **argv)
{
foo(argv); return 0;
}
如果编译这段代码,编译器会发出一条警告信息:
line 5: warning: argument is incompatible with prototype
(第5行:警告:参数与原型不匹配)。
提交代码的工程师想知道为什么会产生这条警告信息,也想知道ANSI C标准的哪一部分讲述了这方面的内容。他认为,实参char *s与形参const char*p应该是相容的,标准库中所有的字符串处理函数都是这样的。那么,为什么实参char **argv与形参const char **p实际上不能相容呢?
答案是肯定的,它们并不相容。
要回答这个问题颇费心机,如果研究一下获得这个答案的整个过程,会比仅仅知道结论更有意义。对这个问题的分析是由Sun的其中一位工程师进行的,其过程如下:
我们先来学习一下ANSI C标准中的一些基本术语:
unportable code(不可移植的代码):
-implementation-defined(由编译器定义的):由编译器设计者决定采取何种行为(也就是说,在不同的编译器中所采取的行为可能并不相同,但它们都是正确的)
例如:当整型数向右移位时,要不要扩展符号位。
-unspecified(未确定的):在某些正确情况下的做法,标准并未明确规定应该怎样做。
例如:参数求值的顺序。
bad code(坏代码):
-undefined(未定义的):在某些不正确情况下的做法,但标准未规定应该怎样做。你可以采取任何行动,可以什么也不做,也可以发出一条警告信息,或者可以中止程序以及让CPU陷入瘫痪,甚至可以发射核导弹(只要你安装了能发射核弹的硬件系统)。
例如:当一个有符号证书溢出时该采取什么行动。
-constraint(约束条件):这是一个必须遵守的限制或要求。如果你不遵守,那么你的程序的行为就会变成像上面所说的属于未定义的。这就出现了一种很有意思的情况:分辨某种东西是否是一个约束条件是很容易的,因为标准的每个主题都附有一个“约束条件(constraint)”小节,列出了所有的约束条件。现在又出现了一个更为有趣的情况:标准规定编译器只有在违反语法规则和约束条件的情况下才能产生错误信息!这意味着所有不属于约束条件的语义规则你都可以不遵循,而且由于这种行为属于未定义行为,编译器可以采取任何行动,甚至不必通知你!
在ANSI C标准第6.3.2.2节中讲述约束条件的小节中有这么一句话:
每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)。
这就是说参数传递过程类似于赋值。
所以,除非一个类型为char **的值可以赋值给一个const char **类型的对象,否则,肯定会产生一条诊断信息。要想知道这个赋值是否合法,就请回顾标准中有关简单赋值的部分,它位于第6.3.16.1节,描述了下列约束条件:
要使上述的赋值形式合法,必须满足下列条件之一:
1.两个操作数都是指向有限限定符或者无限限定符的相容类型的指针;
2.左边所指向的类型必须具有右边指针所指向类型的全部限定符。
正是这个条件,使得函数调用中实参char*能够与形参const char *匹配(在C标准库中,所有的字符串处理函数就是这样的)。
-const char*是一个指向(有const限定符的char类型)的指针。(不能修改其值)
-char*是一个指向(没有限定符的char类型)的指针。
因此,const char*和char*都是指向char类型的指针,只不过,const char*指向的char类型是const限定符修饰的。因此,如下代码
char *cp;
const char * ccp;
ccp = cp;
这样的赋值是正确的,因为
-操作数指向的都是char类型,因此是相容的;
-左操作数具有右操作数所指向的全部限定符(注意理解下,这里右操作数没有限定符,也就是说,有限定符的类型包含没有限定符的类型
,此图只适用于这里的方便理解,别无它用), 同时左操作数自己有限定符const。
-char类型与char类型是相容的,左操作数所指向的类型char具有右操作数所指向类型char的限定符(这里,右操作数所指向的类型为char,右操作数所指向的类型char的限定符为“无”)。这里const为左操作符自身的限定符。
注意,反过来就不能进行赋值。如果反过来赋值,就违反了赋值的约束条件:cp指向的对象的值可以修改,而ccp指向的对象的值不可修改。如果让cp去指向ccp所指向的那个不可修改的对象,如果合法,岂不是变得可以修改了??
如果不信,试试下面的代码:
cp = ccp; /*结果产生编译警告*/ //这样赋值,左操作数指向的类型没有右操作数指向类型的const限定符,
//不符合约束条件2
标准6.3.16.1节有没有说char **类型的实参与const char**类型的形参是相容的?
答案是:没有。
下面我们来分析下 const float *到底是什么?
已知:C的语法规定:const char* 等价于 char const *。
const float *类型并不是一个有限定符的 类型,它的类型是“指向一个(具有const限定符的float类型)的指针类型”,也就是说,const限定符是修饰指针所指向的类型,而不是指针本身。
类似的,const char**也是一个没有限定符的指针类型,它的类型是“指向[指向(有const限定符的char类型)的指针类型]的指针类型”。
由于char **和const char **都是没有限定符的指针类型,但它们所指向的类型不一样。
char **是“指向 char *类型的指针类型”,也就是说,下面代码:
char ** argv;
定义的变量argv指的是:一个指向(指向char类型的指针类型)的指针类型。
const char **类型是“指向const char *类型的指针类型”,也就是说,下面代码
const char **p;
定义的变量p指的是:一个指向[指向(有const限定符的char类型)的指针类型]的指针类型
对于const char** 和char**来说,二者都是没有限定符的指针类型,但是他们指向的类型不是有,前者指向char*, 而后者指向const char*,因此它们不相容,所以char**类型的操作数不能赋值给const char**类型的操作数。
由上可知,显然,(指向char类型的指针类型) 与 [指向(有const限定符的char类型)的指针类型] 是两种类型,所以他们是不相容的。因此,类型为char**的实参与类型为const char **类型的形参是不相容的,违反了标准第6.3.2.2中所规定的约束条件,编译器必然会产生一条诊断信息。
Ref:
《C专家编程》中文版page19
http://www.cnblogs.com/chenleiustc/archive/2011/04/09/2010647.html
如何通过阅读C标准来解决C语言语法问题的更多相关文章
- 【伯乐在线】最值得阅读学习的 10 个 C 语言开源项目代码
原文出处: 平凡之路的博客 欢迎分享原创到伯乐头条 伯乐在线注:『阅读优秀代码是提高开发人员修为的一种捷径』http://t.cn/S4RGEz .之前@伯乐头条 曾发过一条微博:『C 语言进阶有 ...
- go mod 解决 Go 语言的包依赖问题
转:https://testerhome.com/topics/16980 https://testerhome.com/ -------------------------------------- ...
- 一幅图解决R语言绘制图例的各种问题
一幅图解决R语言绘制图例的各种问题 用R语言画图的小伙伴们有木有这样的感受,"命令写的很完整,运行没有报错,可图例藏哪去了?""图画的很美,怎么总是图例不协调?" ...
- 解决python语言在cmd下中文乱码的问题
解决python语言在cmd下中文乱码的问题: a = "再见!"print (a.decode('utf-8').encode('gbk')) #解决在cmd下中文乱码的问题
- 不同标准下的C语言常量范围的默认类型的检测 (测试样例为C90与C99)
不同标准下的C语言常量范围的默认类型的检测 一.C90与C99标准下的不同常量范围的默认类型 C90标准下对不同常量范围默认类型的检测实现及运行结果: C99标准下对不同范围默认类型的检测实现 ...
- 将时区格式的时间转换为易于阅读的标准格式"yyyy-MM-dd"
Date的显示格式就是时区格式, String 标准格式 = new SimpleDateFormat("yyyy--MM--dd").format(new Date());
- Python3爬取起点中文网阅读量信息,解决文字反爬~~~附源代码
起点中文网,在“数字”上设置了文字反爬,使用了自定义的文字文件ttf通过浏览器的“检查”显示的是“□”,但是可以在网页源代码中找到映射后的数字正则爬的是网页源代码,xpath是默认utf-8解析网页数 ...
- 转: 最值得阅读学习的 10 个 C 语言开源项目代码
from: http://www.iteye.com/news/29665 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同 ...
- 最值得阅读学习的 10 个 C 语言开源项目代码
1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连 ...
随机推荐
- 【GoLang】golang垃圾回收 & 性能调优
golang垃圾回收 & 性能调优 参考资料: 如何监控 golang 程序的垃圾回收_Go语言_第七城市 golang的垃圾回收(GC)机制 - 两只羊的博客 - 博客频道 - CSDN.N ...
- MVC Return View() 和 Return PartialView()的区别
分部视图在action中返回一定要用PartialView(),而不要偷懒使用View(),因为,如果你使用View()渲染视图,系统会认为你是一个标准视图,会为你加个默认的母板页(Layout),除 ...
- Max Subsequence
一个sequence,里面都是整数,求最长的subsequence的长度,使得这个subsquence的最大值和最小值相差不超过1. 比如[1,3,2,2,5,2,3,7]最长的subsequence ...
- iOS 关于iphone6 和 iphone6 plus 的适配
http://www.ui.cn/detail/26980.html 根据上面说的,iphone6 plus的屏幕的编程时的宽度应该是414,我理解的也是这样,但是我用iphone6 plus 模拟器 ...
- 关于Intent ,Task, Activity的理解
看到一篇好文章,待加工 http://hi.baidu.com/jieme1989/item/6e5f41d3f65be848ddf9beb9 第三篇 http://blog.csdn.net/luo ...
- 对于Tomcat服务器环境变量和启动配置的一点补充
我们之前第一次使用Tomcat服务器运行jsp应用时,曾经给Tomcat配置过一个环境变量CATALINA_HOME,这个变量指定了Tomcat的安装位置,对于多个开发项目,我们一般会释放多个tomc ...
- nyoj202_红黑树_中序遍历
红黑树 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 什么是红黑树呢?顾名思义,跟枣树类似,红黑树是一种叶子是黑色果子是红色的树... 当然,这个是我说的... & ...
- KMP单模快速字符串匹配算法
KMP算法是由Knuth,Morris,Pratt共同提出的算法,专门用来解决模式串的匹配,无论目标序列和模式串是什么样子的,都可以在线性时间内完成,而且也不会发生退化,是一个非常优秀的算法,时间复杂 ...
- 【leetcode】 Scramble String (hard)★
Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrin ...
- HDU 5879 Cure -2016 ICPC 青岛赛区网络赛
题目链接 题意:给定一个数n,求1到n中的每一项的平方分之一的累加和. 题解:题目没有给数据范围,而实际上n很大很大超过long long.因为题目只要求输出五位小数,我们发现当数大到一定程度时值是固 ...