`

C缺陷和陷阱--阅读笔记(原创)

 
阅读更多

由  王宇 原创并发布

 

第1章词法陷阱

    1.1=不同于==

    1.2&和|不同于&&和||

    1.3词法分析中的“贪心法”


        规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么在读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,指导读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理策略有时被称为“贪心法”或者,更口语化一点,称为“大嘴发”

        a+++++b的含义(a++)+(++b)不是((a++)++)+b原因:a++不能为左值

    1.4整数变量

        141(十进制)0215(八进制)0x12(16进制);01951*8^2+9*8^1+5*8^0

    1.5字符与字符串

        C语言中的单引号和双引号含义迥异

第2章语法陷阱

    2.1理解函数声明

        如何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符

        一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可

    2.2运算符的优先级问题

        表2-1

        前述运算符(()[]->.)、单目运算符、双目运算符

        双目运算符:算术运算符、移位运算符、关系运算符、逻辑运算符、赋值运算符、条件运算符

    2.3注意作为语句结束标志的分号 :一个声明的结束需要有一个分号,struct{};

    2.4switch语句

        break特性

    2.5函数调用 :f();

    2.6"悬挂"else引发的问题

第3章语义陷阱

    3.1指针与数组

        C语言中数组需要注意两点:

        1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来,然而C语言中数组的元素可以是任何类型的对象

        2.对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针

        int*p;inta[10];p=a;这个*p为a[0],错误:p=&a;

    3.2非数组的指针

        char*r,*malloc();r=malloc(strlen(s)+strlen(t));错误原因:

        1.malloc函数有可能无法提供请求的内存.if(!r){}

        2.给r分配的内存在使用完之后应该及时释放,这点务必要记住。free(r);

        3.最重要的,就是调用malloc()函数时,并未分配足够的内存。malloc(strlen(s)+strlen(t)+1);

    3.3作为参数的数组声明

        我们没有办法可以将一个数组作为函数参数直接传递,被转换为指向该数组第1个元素的指针。

        charhello[]="hello";printf("%s\n",hello);等效print("%s\n",&hell[0]);

        intstrlen(ints[]){}等效intstrlen(int*s){}

        main(intargc,int*argv[]){}等效main(intagrc,int**argv){}

    3.4避免“举偶法”:混淆指针与指针所指向的数据   

    3.5空指针并非空字符串

        编译器保证由0转换而来的指针不等于任何有效的指针

        当常数0被转换为指针使用时,这个指针绝对不能被解除引用。换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容

    3.6边界计算与不对称边界 (待续)

        C语言中一个拥有n个元素的数组,却不存在下标为n的元素,它的元素的小标范围是从0到n-1为此

        栅栏错误的两个通用原则:

            1.首先考虑最简单情况下的特例,然后将得到的结果外推,这是原则一

            2.仔细计算边界,绝不掉以轻心,这是原则二

        边界编程技巧:用第一个入界点和第一个出界点来表示一个数值范围。x>=16x<38;--n>=0   

    3.7求值顺序    

        即求值的副作用,C语言中只有四个运算符存在规定的求值顺序:&&||?:,

    3.8运算符&&、||和!

        同按位运算符&|~互换后可以正常工作,实际上是巧合

    3.9整数溢出

        C语言中存在两类整数算术运算,有符号运算与无符号运算

        无符号算术运算中,没有“溢出”一说;一个运算符是有符号,另一个是无符号,有符号的会自动转换成无符号

        当两个操作数都是有符号整数时,“溢出”就有可能发生,而且“溢出”的结果未定义。

    3.10为函数main提供返回值

        return0;

第4章连接

    4.1什么是连接器

        C语言中的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合到一起,但是连接器一般是与C编译器分离的,它不可能了解C语言的诸多细节

    4.2声明与定义

        inta;位置出现在所有的函数体之外,被称为外部对象a的定义;声明并初始化,默认为0;

        externinta;a是一个外部整形变量,从连接器的角度来看,声明是一个对外部变量a的引用,而不是对a的定义

    4.3命名冲突与static修饰符

        static修饰符是一个能够减少命名冲突的有用工具

    4.4形参、实参与返回值

    4.5检查外部类型

        保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任

    4.6头文件

        规则:每个外部对象只在一个地方声明。这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。特别需要指出的是,定义该外部对象的模块也应该包括这个头文件

第5章库函数

    5.1返回整数的getchar函数

    5.2更新顺序文件

        fopen一个文件后,不可以自由地交错进行读出和写入的操作。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用

    5.3缓冲输出与内存分配

        setbuf(stdout,buf);通知输入输出库,所有写入到stdout的输出都应该使用buf作为输出缓冲区,知道buf缓冲区被填满或程序员直接调用fflush,buf缓冲中的内容才实际写入到stdout中

        注意setbufmain

    5.4使用errno检测错误

    5.5库函数signal

第6章预处理器

    宏作用:使多个不同变量的类型可在一个地方说明:

        #defineFOOTYPEstructfoo

        FOOTYPEa;

        大多数C语言实现在函数调用时都会带来重大的系统开销。因此我们也许希望有一种程序块,它看上去像一个函数,但却没有函数调用的开销

    6.1不能忽视宏定义中的空格

        #definef空格(x)((x)-1)f宏无参数,f代表(x)((x)-1)

    6.2宏并不是函数

        定义最大数值宏

        #definemax(a,b)((a)>(b)?(a):(b))缺陷如下:

            max(i,i++)展开则:((i)>(i++)?(i):(i++))副作用

        修正:

        staticinta_temp,b_temp;

        #definemax(a,b)(a_temp=(a),b_temp=(b),a_temp>b_temp?a_temp:b_temp)

    6.3宏并不是语句

        #defineassert(e)if(!e)assert_error(__FILE__,__LINE__)       

        if(x>0&&y>0)

            assert(x>y);

        else

            assert(y<x);

 



        展开错误

        if(x>0&&y>0)

            if(x>y)assert_error("foo.c",37);

            else

            if(y<x)assert_error("foo.c",38);

 



    6.4宏并不是类型定义

        #defineT1structfoo*

        typedefstructfoo*T2

第7章可移植性缺陷

    7.1应对C语言标准变更

    7.2标识符名称的限制

        ANSIC标准所能保证的只是,C实现必须能够区别处前6个字符不同的外部名称。而且,这个定义中并没有区分大写字母与其对应的小写字母

        intFun(int)

        {

            intp=10;

            p=fun(p);

        }

 



        合法的递归

    7.3整数的大小

        8位char1字节

        32位intlongfloat4字节

        16位short2字节

        64位double8字节

    7.4字符是有符号整数还是无符号整数

        8位的取值范围:

            有符号:-128~127高1位为1是负数,0是整数

            无符号:0~255

            二进制的表示方法,同正负符号的存储不同,二进制是补码,正负符号的存储同上

                反码:所有位求反

                补码:反码加1

        常见错误认识是:如果c是一个字符变量,使用(unsigned)c就可得到与c等价的无符号整数。这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果

        正确的方式:(unsignedchar)c

    7.5移位运算符

        如果被移位的对象是无符号的数,那么空出的位将被0填充

        如果被移位的对象是有符号的数,那么既可以用0填充空出的位,也可以用符号位的副本填充空出的位

        移位计数必须大于或等于0,而严格小于n(n长度,例如intn=32)

    7.6内存位置0

        在所有的C程序中,误用null指针的效果都是未定义的

    7.7除法运算时发生的截断

    7.8随机数的大小

        ANSIC标准,定义的随机数的最大值:RAND_MAX

    7.9大小写转换

        #definetoupper(c)((c)+'A'-'a')

        #definetolower(c)((c)+'a'-'A')

        依赖于特定实现中字符集的性质,既需要所有的大写字母与相应的小写字母之间的差值是一个常量,ASCII字符集符合

    7.10首先释放,然后重新分配

        malloc()free()realloc()

    7.11可移植性问题的一个例子

第8章建议与答案

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics