前言:这是一本老书了,大概写于1994年,但是其中大部分内容至今仍可以参考。我不知道这本书为什么被称为“鱼书”,但是里面很多生动的例子都很引人入胜,可以说,这是读起来最轻松的一本技术书了,我用了两天一口气读完。感觉如果很多书籍都能写的这么轻松,或许读起来就不那么累。
其中有一段话让我觉得像个笑话:
“数组的下标应该从0还是从1开始?我提议的妥协方案是0.5,可是他们未予认真考虑就一口回绝。——Stan Kelly Bootle”
-------------------------------------------------
1 char**与const char**参数的关系不同。
http://blog.csdn.net/baizhengbiao/article/details/7341666
2 gets被废弃,用fgets,其实很早就知道,但是还是偶尔会用,这回要尽量避免用了,提醒自己。
3 考虑优先级,尽量使用括号。(提到了最早的C语言&与&&是不区分的,后来区分,为了兼容,所以优先级之间不能插入其它运算符)
4 开篇引入的时间函数的操作,这个确实值得注意,时区的问题。
5 函数传参要用寄存器,而不仅仅是简单的将参数压入堆栈,当然结构体参数很可能是压入堆栈的。
C语言支持结构体作为函数返回值的特性,可以在需要返回一个数组的时候,用结构体包一下传递回来。
6 define和enum区别,define的东西直接替换为值,而enum在调试的时候还有符号存在。
7 让我们尝试一下如何解析void ( *signal( int sig, void( *func )(int) ) )(int)
首先这是一个void (*signal( .... ) )( int ),表示signal是一个函数,返回一个函数指针,这个函数指针是void (*f)( int )类型。
signal内部参数第一个是int,第二个是void (*func)(int),也是一个函数指针。
其实只要明白,在C语言里,某些类型是“外包”的就可以了,就是它不能简单地写在左边或右边,必需左右都写。例如java中有
int[] a;和int a[];
左边的形式就是都写到一边,而右边就是“外包”形式,把a包在中间了。
8 一个不错的宏定义#define STRCMP( a, R, b ) ( strcmp( a, b ) R 0 )
9 编译器-W选项后面跟一个字母,表示要给那个阶段传递参数,然后一个逗号,后面跟参数,例如
-Wl,-rpath=...
表示给linker传递rpath参数,参数值为...
10 静态库生成可执行文件时,只是拷贝需要的函数,不会把整个库放进exe。
动态库的目的之一是ABI( Application Binary Interface ),提供稳定的接口。
动态库的生成和使用:
gcc -o libfruit.so -G fruit.c
gcc main.c -L/home/gogdizzy -R/home/gogdizzy -lfruit
位置无关代码PIC(position independent code),实质是设置一个全局表和偏移量,只需要更改偏移量,就可以将动态库放入内存,但是变量需要解引用,时间效率低。
11 nm命令可以查看动态库函数列表,也可以查看可执行文件各个段信息,例如:
nm -s -fsysv ./a.out
12 interpositioning代码,就是用自己定义的同名函数替代库函数。
13 可执行文件中段的概念和x86 cpu架构中段的概念不同,一个是内容块的描述,一个是寻址模式。
BSS表示(Block Started by Symbol),这个段内的数据初始值为0。生成object文件时,这个段只需要记录一个大小,不需要真正分配空间(也就是说.bss段不分配,但是.data段分配)。所以有人又称其better save space,当生成可执行文件时,同样也不需要分配空间,只需要记录这个段的大小。在启动进程的时候,告诉操作系统需要多少空间,那么操作系统就会在内存中开辟一个全为0的空间,映射到这个地址即可。
https://en.wikipedia.org/wiki/.bss
14 通过alloca分配的内存位于堆栈中
15 setjmp和longjmp配合使用,如果longjmp设置的返回值是0,那么实际setjmp得到的返回值是1
在setjmp和longjmp之间改变的局部变量,如果想在jmp回去后保持这个改变,需要声明为volatile的。这是因为局部变量可能被优化成寄存器,而执行longjmp以后有个恢复寄存器的行为。
http://stackoverflow.com/questions/1393443/setjmp-longjmp-and-local-variables
可用来做错误处理,与C++的catch和throw类似。
http://blog.csdn.net/cscmaker/article/details/7584433
16 运行了7.3给的例子,64位linux下申请的太恐怖了,看到res( resident memory )是1.2G,但是virt( virtual memory )已经超过几百G了,系统很卡。
这说明malloc并没有真正的使用内存,只是在系统做了一个记录,因为硬盘也没这么大。
17 Cache分为许多行,填充于同一Cache行的地址都是该Cache行大小的整数倍。注意某些情况,两块内存地址都被填入同一Cache行,无法同时存在于Cache Line,从而无法优化。
18 第7章末尾的意思应该是printf函数是不可重入的,如果在printf执行期间进入了handler函数,该函数里面又调用了printf,就会引发问题。
标准中,在信号处理函数中调用库函数的行为是未定义的。
19 debugging hooks,写一个遍历某个复杂数据结构的函数,但是不在主流程中调用,但是在调试器中输入函数名称,就可以打印。