语言本身只不过是一个工具,写出高质量的软件不光是要了解语言本身,
更要对软件工程、数据结构、操作系统等有所了解。
1. 要避免潜在的隐式类型转换。在做算术运算时变量的数据类型要一致,
如果既有有符号型变量也有无符号型变量那么有符号型负数会隐式转换为大正数;
2. 函数和变量要保证定义和声明的一致性,
这样可以消除编译器编译代码时发出的警告同时能够提高软件的可移植性;
3. Char *p = “string constant”; p[0] = 0; 会导致程序crash;
因为编译时会将"string constant"这个字符串常量放入rodata区域,
向rodata写数据会发生非法访问错误(SIGSEGV);
4. typedef可以用来给复杂的数据类型比如struct/enum等数据类型取一个抽象的别名;
它是在编译时检查,所以相对于define更容易检查出错误;
5. C语言也可以实现面向对象的设计方法,
把函数指针作为成员加入到结构体中就会使得结构体有种“类”的感觉了;
c语言也可以通过将含有基类的结构作为开始的子集来实现继承;
但是它却没办法做到面向对象语言的重载和覆盖;
所以如果要实现面向对象的程序设计,请使用面向对象的语言;
6. c语言编译器没有提供比较两个结构体变量的方法;
如果需要比较结构体是否相等必须由程序员自己来实现结构体按域来比较;
7. Sizeof运算符计算结构体的长度时有可能比结构体本身的长度要长,这可能是为了数据对齐而进行了尾部填充;
另外这也有可能是依编译器而定,不同的编译器可能会产生不同的结果;
8. 通过打印的枚举类型变量值有时候很难一眼看出该值所对应的含义,
c语言没有简单的方法让我们很容易的显示枚举值的符号;
但是现在比较好的调试工具,可以自动显示枚举常量值的符号;
9. 使用指针时,必须要考虑到这根指针到底指向了哪块内存空间,是否需要为该指针分配内存空间;
10. 指针和指针指向的对象是完全不同的;
比如函数内部定义的指针因为是在栈上分配的所以在函数返回时就会自动释放,
而指针指向的对象是在堆上分配的所以必须由调用者手动释放;
养成良好的习惯,在释放指针指向的内存空间时记得将指针变量也置为空指针,
否则就有使用野指针的风险;
11. 一个真正的操作系统会在程序退出时回收所有的内存和其它资源,
所以在推出程序时程序员没必要显示的去释放那些已经分配的内存;
12. 使用libc库,当malloc申请内存时它并不是真正的从操作系统中分配内存,
当free内存时它也不会立即返回给操作系统。
所以从操作系统的角度来看,当你分配和释放内存时,内存的占用率可能并没有发生变化;
13. Malloc/free所管理的内存会有一个数据结构,它会记录每一块已分配的内存块的大小。
所以当free一块内存时,只需要这根指针就能查到要free的内存块的大小;
14. 整的头文件搜索规则是怎样的?如果是标准或系统提供的头文件,则会先从一个或多个标准位置进行搜索;
它类似于linux系统的命令查找规则一样,它会从由环境变量PATH中指定的路径中进行搜索;
15. 写出与字节序无关的代码会具有更好的移植性;
16. Const char *p与char* const p,有待进一步了解;
17. 标准对无定义行为没有强制要求,所以编译器就可以对该未定义行为做任何事情;
不同的编译器可能会产生不同的结果,这就是经常所说的执行结果依赖于编译器的情况;
这样的代码不具有可移植性,而且也不能假定程序员对编译器很了解,所以这种代码不建议被采纳;
18. ANSI标准中为何有如此多未定义的东西?
这种有意的不严格规定可以让编译器生成效率更高的代码,而不必让所有程序为了不合理的情况承担额外的负担;
编程语言标准可以看作是语言使用者和编译器实现者之间的协议,
只有协议双方都恪守自己的保证,程序就可以正确运行;
19. Fgetc/getc的返回值是int类型,而不是char类型,这点要特别小心;
20. 反斜杠\是编译器的转义字符,可能也有一些其它的系统或语言比如linux shell使用反斜杠做转义字符;
但是printf的转义字符是%;
21. 如果不能确认一种数据类型的长度,比如size_t,那么在做这种数据类型的数据转换时,
需要将其转换成一个已知的长度够大的类型,否则就有可能出现数据被截断的可能;
22. 如果要写一个可变参数的函数,可以借助<stdarg.h>提供的辅助设施;
除了常见的printf等标准化输入输出函数外,这样的用法并不很常见;
23. 访问地址未对齐的指针会发生什么;
24. 尽量遵循已经形成的代码布局风格,不要让自己写的代码看起来和已有的代码格格不入;
要知道代码的风格、代码的可读性其实会直接影响到软件的维护成本以及系统的健壮性;
25. 有些编译器和lint对于被丢弃的返回值会报警告,
所以显示的使用void做类型转换相当于告诉编译器这是由程序员主动的去忽略返回值的,
对于其它忽略返回值的情况请继续提出警告;
26. 程序设计风格,它是某种程序的艺术,不要被僵化的教条所束缚;
就像goto语句,很多人建议禁止使用它,但是有时候它又很有用;
对这种所谓的程序风格的争论,并且争论双方都不会妥协的争论,是毫无意义的;
27. 许多C编译器实际上只是半个编译器,它们选择不去诊断许多源程序中不会妨碍代码生成的难点;
所以编译代码时产生的很多警告选项实际上很多都是软件中潜藏的风险,它们总会选择在某个时机点报复程序员;
所以修复编译代码产生的警告选项是很有意义的;
28. 验一个文件是否存在的c库函数有stat/access/fopen等,其中只有fopen具有广泛的可移植性;
29. System函数只接受一个单独的字符串参数来表述调用程序;
如果你要建立复杂的命令行,可以使用sprintf;
30. 一个进程可以通过系统提供的setenv/putenv等函数来改变自己的环境,
被改变的环境通常也会被传入到子进程,但是这些改变不会被传递到父进程中;
31. If (signal(SIGINT, SIG_IGN) != SIG_IGN)signal(SIGINT, func);
32. 无论是八进制、十进制或者十六进制,整数在系统中都是以二进制存储的;
数字表达的进制只有在读入或写出到外部世界时才起作用;
33. 函数调用,虽然明显比内联代码要慢,但是它对程序的模块化和代码清晰度的贡献,从而根本没有理由来避免它;
34. Assert用来测试断言,其本质是写下程序员的假设,如果假设被违反,则表明有个严重的程序错误;
断言会中断程序的执行,断言也不应该被用来捕捉意料中的错误,比如malloc总是有可能分配内存失败的;
35. 语言只是为了帮忙我们的工具,根本没有必要沉迷于语言本身的语法技巧和某些特殊的用法,
除非你能够明确知道你需要使用它们;
学无止境!