liuchuanwu的个人博客分享 http://blog.sciencenet.cn/u/liuchuanwu

博文

《C陷阱与缺陷》读书笔记

已有 3154 次阅读 2014-1-21 13:08 |个人分类:C语言|系统分类:科研笔记| C语言, 读书笔记, 陷阱与缺陷

   《C语言缺陷和陷阱》中文版的读书笔记,和我关于C语言编译系统实现的一些杂乱思考及我的答案。


第一章,词法陷阱

    C语言词法分析:

    C语言的符号可能包含一个或多个字符。C语言编译器做词法分析时认为一个符号尽可能包含更多的字符。C语言从左到右逐个读入字符,如果一个字符可以和前面的字符组成符号,则继续读入直至不能所读字符不能与前面字符组成任何符号。因为读入的空格和换行等也是有效字符,所以C语言符号间不可有空格和换行等符号。如

    num = 11; printf(); a == 1;//正确

    num = 1   1; pr  intf();  a = = 1;//错误。

   注意因字符的结合顺序而产生的词法的二义性,如:

   x / *p; //x除以指针p所指的值,

   x/*p; // 其中的“/*”系统认做注释,系统会继续读入字符直至“*/”为止

  注意整形常量以0为开头被认做八进制数,以0x开头被认做十六进制数字。所以010和10不同。


第二章、语法陷阱

1,函数声明

   函数的类型为函数返回值类型。变量和函数的声明可以使用括号。如:

    float ((f)); //f为浮点变量

    float (ff()); //f为返回值为浮点数的函数

   函数声明时,函数名后的括号优先级高于*,以下语句含义不同

   float *g(); //相当于*(g()),g为函数,返回值为指向浮点数的指针

    float (*h)(); //h为指向函数的指针,所指向的函数返回值为浮点数。

   一旦知道如何声明一个指定类型的变量,那么该类型的类型转换符就很容易得到了。只要把声明中的变量名和声明末尾的分号去掉,并用括号封装起来就成为一个类型转换符,如由float (*h)()可以得到(float (*)()),表示一个“指向浮点型函数的指针”类型转换符

   设fp为函数指针,调用其所指的函数使用

  (*fp)();

fp是指针,*fp是该指针指向的函数,(*fp)()就是调用该函数。此语句*fp两边的括号非常重要,如果去掉则变成*fp();相当于*(fp()),ANSI C把它做为*((*fp)())的简写(what?)。

   例:调用存储位置为0的子函数。方法一:声明一个指针fp,指向一个空函数。将其初始化为0,并调用

         void (*fp)();

         (*fp());

        方法二:将常数0转换为指向返回值为void的函数的指针类型(void (*)()) 0,并调用

     (*void (*)())0); //末尾的分号使表达式成为一个语句

        方法三,使用typedef

        typedef void (*funcptr)();

        (*(funcptr)0)();

2、运算符的优先级

   运算符的优先级有15个:

   (1)、优先级最高的并不是真正意义上的运算符,包括数组下标、函数调用、结构成员等。

   (2)、单目运算符的优先级仅次于以上运算符,他们在真正意义的运算符中优先级最高。单目运算符自右向左结合。

   (3)、接下来是双目和三目运算符,优先级从高到低依次是算术运算符、移位运算符、关系运算符、逻辑运算符、条件运算符(三目)和赋值运算符。

   (4)、逗号运算符优先级最低。

例,int *p;

    *p++ = 100;//将100赋予*p,然后指针向后移位。

3、注意作为语句结束标志的分号

   (1)、单独的分号作为一个空语句。

   (2)、多写的分号有时造成Bug,如在if或while语句之后如果多的分号。相当于{}

   (3)、少写的分号也会造成Bug,如在return后少写分号可能会返回return后的表达式。在结构体定义后少写分号可能将其后的main函数定义为此结构体类型。

4、注意switch语句case后的break

   当没有break时,switch语句会执行被match的常量后所有语句,包括气候的case。

5、函数调用要有括号

   如果f是函数,f();调用函数。而f;只是求出f的地址而没有调用。

6、悬挂else引发的问题

   注意if else语句的配对。else始终与同一大括号内最近的未配对的语句配对。

第三章、语义陷阱

1、指针与数组

   重要!见书。
2、字符串指针

   注意字符串结尾的空字符""。

3、指针的声明

   char *p;和char p[];等价。char **p;和char *p[];等价。

4,注意求值顺序。

&&和||运算符只有在需要时才对右侧操作数球值。条件运算符?:根据条件对第二或第三个操作数求值。逗号运算符,首先对左侧操作数求值,然后将该值被弃,再对右侧操作数求值。赋值运算符并不保证任何求值顺序(=左侧并不一定比右侧先求值)。如

i = 0;

while(i<0)

   y[i] = x[i++];

并不保证y[i]在i自增之前被赋值。同理

i = 0;

while(i<n)

   y[i++ = i];

也不正确。正确写法是

i = 0;

while(i<n) {

   y[i] = x[i];

   i++;}

当然这种写法可以简化为

for(i = 0; i < n; i++)

   y[i] = x[i];

5、整数溢出

   整数发生溢出时,所有关于结果的假设都不再可靠。如一下判断两个非负整数之和是否溢出的方法

if (a + b < 0)

   complain();

并不能正常运行。一种正确的方式是

if((unsigned)a + (unsigned)b > INT_MAX)

   complain();

其中INT_MAX是一个以定义常量,代表允许的最大整数值。ANSI C标准在<limits.h>中定义了INT_MAX。另一种可行写法是

if(a > INT_MAX - b)

   complain();

6、为main函数提供返回值也

   main函数的默认类型为整形。main的返回值有时很重要。大多数C语言实现都通过main的返回值来告知系统函数执行是成功还是失败。返回0代表成功,返回非0代表失败。除非是void 类型,否则应该设定返回值。


第六章、预处理器

1、注意宏定义中的空格。宏的各个参数和整个表达式应该用括号括起来。

2、宏不是函数。

3、宏不是类型定义。


参考:

Andrew Koening著. 高巍译. C陷阱与缺陷. 北京:人民邮电出版社,2008



https://wap.sciencenet.cn/blog-1005104-760971.html

上一篇:C语言的字符串和指针
下一篇:C语言杂笔记

0

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2021-5-13 14:47

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部