Andrew Koening 著作

第一章 词法陷阱

=不等于==

=是赋值运算符
而==是判断用的双目运算符

贪心法 词法分析

编译器会拿尽可能多的字符组成符号。
a—-b会被编译器解释为a — - b
如果编译器支持=+和+=一致,
那么a=-1会变成a = a - 1;

整形常量

0开头的是八进制而非十进制,对齐时需要注意

字符串和字符

双引号引出的是一个指针,在堆区的指针
而单引号的本质是整数,存在char的一个整数。

  1. char *str = 'x';
  2. 是错误的写法

第二章 语法陷阱

判断

C语言使用数值判断,0为假,非零为真,NULL属于0,

优先级

C语言的符号具有一定优先级,括号的优先级最高,可以通过加括号的方法控制未知 的优先级。

分号

分号是语句结束的符号。
循环语句默认执行下一条语句,因此循环语句本身不能加括号。

switch语句

switch语句需要加break,但是如果刻意设计时,需要注释说明这里没有break

函数名

函数名加上括号才能调用函数,只有函数名只代表函数的地址。

悬挂else

当if是是外层if时且没有else跟随,需要加上大括号防止悬挂else导致程序逻辑出现问题。

第三章 语义陷阱

内存分配失败

使用malloc分配内存时需要验证是否分配失败。
如果分配失败的情况,需要返回错误信息。如果不增加if语句判断是否成功,可能会报出取消对NULL指针的引用警告。

字符串计数

字符串是组类型,strlen可以分析字符串的长度,但是没有包含那个NULL结尾。所以在给strlen分配长度的时候需要加上一个单位来容纳\0这个结束符号
char *是一个指针类型,
char []是个数组类型
因为传值引用的时候数组只传首地址,所以需要拿指针去接收
但是指针可以直接操作。

举隅法

对于char *p = “Xyz”;来说
实际上是四个空间
Xyz\0这四个,所以p[3]==0
但是这种写法并不提倡

边界

栈区在定义声明时必须考虑边界问题,尤其是数组边界在循环中操作时
例如这个边界问题:

  1. #include<stdio.h>
  2. #include<malloc.h>
  3. #define MAX_HEAP 4
  4. int main(){
  5. int *pHeapBlock = NULL;
  6. int *pCursor = NULL;
  7. int arr[MAX_HEAP] = {0};
  8. int i;
  9. pHeapBlock = (int *)malloc( sizeof(int) * MAX_HEAP);
  10. pCursor = pHeapBlock;
  11. for(i = 0;i < MAX_HEAP;i++){
  12. *pCursor = arr[i];
  13. pCursor++;
  14. }
  15. pCursor = pHeapBlock;
  16. for(i = 0;i < MAX_HEAP;i++){
  17. printf("%d\n",*pCursor);
  18. pCursor++;
  19. }
  20. free(pHeapBlock);
  21. return 0;
  22. }

相对于上面的程序,下面的代码部分的健壮性就弱的多

  1. int *pHeapBlock = NULL;
  2. int *pCursor = NULL;
  3. int arr[10] = {0};
  4. int i;
  5. pHeapBlock = (int *)malloc( sizeof(int) * 4);
  6. pCursor = pHeapBlock;
  7. for(i = 0;i <= 4;i++){ //这个4是边界值,如果改成大于4的其他数,将会引起未知错误。
  8. *pCursor = arr[i];
  9. pCursor++;
  10. }
  11. pCursor = pHeapBlock;
  12. for(i = 0;i < 4;i++){
  13. printf("%d\n",*pCursor);
  14. pCursor++;
  15. }
  16. free(pHeapBlock);

整数溢出

CSAPP中解释了整数溢出的情况,分为正溢出和负溢出

第四章 链接器

链接器是链接文件和代码之间的编译工具。
extren可以重复声明,但是只能定义一次
static是静态变量。
auto是自动变量,但是大部分时候编译器都会忽略auto说明或是自动增加
register是寄存器变量,可以提高一些效率,但是编译器决定哪些register要储存,哪些不储存。

第五章 库函数

库函数提供了很多封装了的功能和函数。
但是函数定义的一些标识需要我们谨慎使用。
例如用来储存EOF的变量是否会溢出。
函数指针的指向和使用是否正确。

第六章 预处理器

预处理器会处理宏和变量。
但是宏不是语句,而是编译器规则,所以使用宏的时候需要注意。