读 《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  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容