如何通过阅读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万个并发连 ...
随机推荐
- yum服务器搭建(深入理解yum工作原理)
作者:firefoxbug 时间:July 27, 2014 分类:Linux 前言 在前面一篇rpm包制作描述了rpm的打包过程,这篇文章主要讲述yum的工作原理. yum 运行原理 yum的工作需 ...
- C++ 代码头注释模板
/********************************************************************************* *Copyright(C),You ...
- C#零碎知识汇总
1.1取时间差 时刻:DateTime 时差:TimeSpan 代码: DateTime time1 = DateTime.Now; textBox1.Text = time1.ToStri ...
- 十一天 python操作rabbitmq、redis
1.启动rabbimq.mysql 在""运行""里输入services.msc,找到rabbimq.mysql启动即可 2.启动redis 管理员进入cmd, ...
- 请确认 <Import> 声明中的路径正确,且磁盘上存在该文件。
在网上下了个源码打开报错. 请确认 <Import> 声明中的路径正确,且磁盘上存在该文件. 一查,原来是路径错误. 解决办法:将项目文件(.csproj)用记事本打开,然后找到<I ...
- 如何手动添加Windows服务和如何把一个服务删除
windows 手动添加服务方法一:修改注册表 在注册表编辑器,展开分支"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services" ...
- 【jquery】一个简单的单选、多选、全选、反选、删除的小功能
对表格内容进行单行删除.单行选中.多行选中.全选.反选.删除选中行等操作 HTML代码 <table class="table table-bordered border-shadow ...
- php数据访问基础
建一个连接(造一个连接对象) $db = new MySqli("IP地址或者域名,若果是本地则用localhost","用户名","数据库密码&qu ...
- php图片防盗链的小测试
test.php <?php $txt = "http://hiphotos.baidu.com/stupidet/pic/item/4f1b8cfb4c33b7254e4aea69. ...
- A Horrible Poem(bzoj 2795)
Description 给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节.如果字符串B是字符串A的循环节,那么A可以由B重复若干次得到. Input 第一行一个正整 ...