读 《C Traps and Pitfalls》Record

@(C 语言)[基础, 编程]

薄薄一本书, 却记录了c 编程经常犯下的错误,再读,记录下。

词法

  • 词法分析 : 大嘴法
    编译器分解符号的方法是从左到右读入, 判断可能组成的最大的一个符号
a---b // (a--) - b

别复杂化, 使用括号,清晰直观

  • 字符和字符串
char* pStr = "YES" // 'Y', 'E', 'S', 0 , 4 char
char ch = 'y'// a char

单引号实际代表一个整数
双引号代表指向无名数组的起始字符的指针(字符结尾 0)
使用库函数计算得到的字符串长度不包括结尾的0!

语法

  • 理解函数声明
    调用首地址为0的子例程 (void fun()),
(* (void(*)())0 )();

// call void fun()
// 声明指向上述fun的指针
void (*pfun)();  
// 注意括号,void *rf(); 声明了返回void* 指针的函数。
pfun = &fun;// pfun = fun; 也可以
(*pfun)();
pfun(); // 简写

// 对0地址进行类型转换
pfun = (void (*)())0;
没必要多声明一个‘哑’变量pfun,因此如开端所写,对0进行类型转换,再解引用调用

// 直观的话
typedef void (*pfun)();
(* (pfun)0 )();

准则 : 按照使用的方式来声明

  • 运算符优先级问题
    对于这个问题,优先级记归记,不熟悉建议使用括号来显示保证。
    记录下,吃过这个亏不止一次:
if (flag & 0x01 != 0) {}...
//want 
if ((flag & 0x01) != 0) {}...
//actualy 
if (flag & (0x01 != 0)) {}...

r = hi << 4 + low;
//watnt 
r = (hi << 4) + low;
// actually 
r = hi << (4 + low);

a = *p++;
// == *(p++)  <——
a = *p; p++;

记录优先级可以写出更加优雅的代码, 不装X。

  • 语句结束分号
//少写出错
if (i < 2)
    return
i = 2;
//->
if (i < 2) return (i = 2);

//多写出错
if (i < 2);
    a = 1;
//->
if (i < 2);
a = 1; // every time
  • switch 别漏 break !!!!

  • if else 的对应关系
    else 始终与同一对括号内最近的未匹配if 结合

if (x == 0) 
    if (y == 0 ) {..}
else {
    ..
}
//  不同所缩进, 实际是
if (x == 0) {
    if (y == 0) {..}
    else {..}
}

避免悬挂式else, 使用{}进行匹配

if (x == 0) {
    if (y == 0 ) {..}
} else {
    ..
}

语义

  • 指针和数组
  1. C语言中只有一维数组
int a[12]       // 12个 int
int b[12][31]   // 12个 int[31]类型数组的元素
// sizeof(a) == 12 * sizeof(int);
// sizeof(b) == 12 * (31 * sizeof(int))

// attention 
/*
* 数组名是数组的首地址(符号表中对应地址)
* 数组操作 :数组地址 + 偏移地址 --> 内容
* 指针操作 : 指针 --> 数组地址 + 偏移 --> 内容
*/
int *p  = a; 
//sizeof(p) == sizeof(int*) // sizeof of pointer(32位 4, 64位 8)
//sizeof(*p) ==  sizeof(int)

对数组取sizeof可以得到数组的大小,但是对其他指针取sizeof取到的是平台地址的长度

2.数组根据他自身的类型,决定其在加减时,实际增减的内存地址值

int *p0 = (int*)0x0000;
char *p1 = (char*)0x0000;
++p0; ++p1;  // p0 += 1; p1 += 1;
//p0 == 0x0004; p1 == 0x0001;  // if sizeof int == 4

指向数组的的指针

char array[12][31];
char (*p)[31];
char **pp = array;  //注意这里
p = array;
p[0] -->array[0];
++p  -->array[1];

假设array在地址0x0000,那么p 也是地址0x0000, ++p后,p指到array[1],所以地址就是array + sizeof(char)*31; 但是,pp,指向指针的指针,一开始也是指向array[0], 但是,++pp后,指向的和p不一样,而是加了一个sizeof(char*)的长度,因为他的单位是一个指针大小,而p的单位是一个char charxx[31]的大小。

所以,请告诉指针,我指的到底是谁?

  • 非数组的指针
char *s = "jj";
char *t = "xx";
//1 :strlen 不包括字符串结束字符
char *r = (char*)malloc(strlen(s) + strlen(t) + 1); 
if (!r) {
    //2 : malloc 可能申请失败
} else {
    strcpy(r, s);
    strcpy(r, t);
    //..
}

//3 : 记得释放!
free(r);
  • 数组作为参数传递给函数,已经转换为指针。
  • 复制指针不等于复制指针指向的对象,东西只有一份,只是多了一个别称。
  • 数组边界问题(左闭右开, 在一些用变量去索引的情况下,没有处理好,导致访问读取未位置的内容带来的错误。)
    提到这种不对称数学角度来说不优美,却给程序设计带来了一些简化。
for (int i = 0; i < 6; ++i) {
...
}
1, 上下边界差就是元素数目
2, 上下相等,范围为空
3, 即使取值为空,上边界永远不小于下边界
  • main 函数返回0代表执行成功,其他地方也可以默认这种操作,返回0代表成功。

连接

  • 定义一处, 拿到内存; 其他地方使用, 声明
  • 避免冲突,限定作用域,使用static

库函数

...

预处理器

  • 注意宏定义错误空格
#define f (x) ((x) -1)
// #define f   ((x) ((x) - 1)) 

宏定义中恰当使用括号,避免实际使用展开后由于优先级而带来的错误。

  • 宏不是语句
#define assert(e) if (!e) assert_error(__FILE__, __LINE__)
if (..)
    assert(..);
else
    assert(..);

// 展开
if (..)
    if (..) assert_error(__FILE__, __LINE__)
else
    if (..) assert_error(__FILE__, __LINE__)
    
// 参考前面悬挂式else, 整理后看到实际效果不一致
if (..)
    if (..) 
        assert_error(__FILE__, __LINE__)
    else
        if (..) 
            assert_error(__FILE__, __LINE__)

可移植性缺陷

其他

  • static
    1,函数内的变量,静态变量, 作用域限定在该函数的“全局变量”, 函数退出也保存在内存,下次调用仍能使用该值
    2,模块内的变量,限定该模块内使用
    3,模块内的函数,限定该模块内使用

  • const
    不可改,保护,避免意外修改不想被改变的数据

  • volatile
    易变的, 避免被优化
    如果一个变量存在在程序流程外被改变(多线程,中断等)声明为volatile保证每次都从内存读取,避免编译器优化,带来数据更新不一致等问题,保证访问的稳定。

char fifo[SIZE];
// 指针指向,不能通过指针修改fifo内容
char const *pff = fifo;
// 指针指向,不能修改指针内容, 即保证pff == fifo 永远
char * const pff = fifo;
// 修饰谁,靠近谁。

// pff 不可变,*(pff + xx) 随时可能改变。
static volatile char* const pff = fifo;
  • 符号数扩展带来的问题
char a = 0x03;
char b = 0x81;
uint16_t c = a <<8 | b;    
// c = 0xFF81 --> 
// b 变成16bit 的时候,char b是有符号数,高位填充1(填充符号位)
unsigned char a = 0x03;
unsigned char b = 0x81;
uint16_t c = a <<8 | b;    //  c = 0x0381  

你可能感兴趣的:(读 《C Traps and Pitfalls》Record)