C语言深度剖析读书笔记_第1页
C语言深度剖析读书笔记_第2页
C语言深度剖析读书笔记_第3页
C语言深度剖析读书笔记_第4页
C语言深度剖析读书笔记_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C语言深度剖析读书笔记第1章关键字1.1、定义与声明的区别:定义创建了对象并为对象分配了内存,声明没有分配内存1.2、register请求编译器尽可能将变量存在CPU寄存器中以提高访问速度,register变量必须为CPU寄存器所能接受的类型,它须是一个单一的值,并且长度=整型的长度,由于register变量可能不放在内存中,故不可以用”&”来获取它的地址1.3、函数前面加static使得函数成为静态函数,它的作用域仅限于本文件中,故又称内部函数1.4、case关键字后面只能是整数或字符型的常量或常量表达式。 const int a = 5; case a: /const只读变量,编译出错,c

2、ase label does not reduce to an integer constant case 1.1: /小数,编译出错,case label does not reduce to an integer constant case 3/2: /没有问题,分数会被转换成整数1.5、“跨循环层”的概念本身是说,由外层循环进入内层循环是要重新初始化循环计数器的,包括保存外层循环的计数器和加载内层循环计数器,退出内层的时候再恢复外层循环计数器。把长循环放在里面可以显著减小这些操作的数量,还可以增加cache的命中率。在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外

3、层,以减少CPU跨切循环层的次数。for(i = 0; i 50; i+)for(j = 0; j 10000; j+)效率比下面这个高for(i = 0; i 10000; i+)for(j = 0; j 50; j+)1.6、void指针的算术操作void *pvoid;pvoid+; /ANSI认为是错误的,因为它认为进行算术操作的指针必须知道它所指向的数据类型大小pvoid += 1; /ANSI认为是错误的/但GNU指定void *的算术操作跟char *相同。1.7、const编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,

4、没有了存储与读内存的操作,使得它的效率也很高。例如:#define M 3/宏常量const int N=5; /此时并未将N放入内存中.int i=N;/此时为N分配内存,以后不再分配!int I=M;/预编译期间进行宏替换,分配内存int j=N;/没有内存分配int J=M;/再进行宏替换,又一次分配内存!const定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,在程序运行过程中只有一份拷贝。#define定义的宏常量在内存中有若干个拷贝。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值怎么看const修饰哪个对象先忽略类型名(编译器解析

5、的时候也是忽略类型名)。看const离哪个近。离谁近就修饰谁。const int *p; /const *p/const修饰*p,p是指针,*p是指针指向的对象,不可变int const *p; /const *p/const修饰*p,p是指针,*p是指针指向的对象,不可变int *const p; /*const p/const修饰p,p不可变,p指向的对象可变const int *const p; /前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变1.8、volatile编译器遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊

6、地址的稳定访问先看看下面的例子:int i=10;int j = i;/(1)语句int k = i;/(2)语句这时候编译器对代码进行优化,因为在(1)(2)两条语句中,i没有被用作左值。这时候编译器认为i的值没有发生改变,所以在(1)语句时从内存中取出i的值赋给j之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k赋值。编译器不会生成出汇编代码重新从内存里取i的值,这样提高了效率。但要注意:(1)(2)语句之间i没有被用作左值才行。再看另一个例子:volatile int i=10;int j = i;/(3)语句int k = i;/(4)语句volatile关键字告诉编译器i是

7、随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k中。这样看来,如果i是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile可以保证对特殊地址的稳定访问。1.9、大部分编译器中,默认情况,enum会转化为intenum ColorGREEN = 1,REDCol故sizeof(Col) = sizeof(int)第2章、符号2.1、注释int /*.*/i; /编译器会用空格代替原来的注释,这里相当于 int i 编译能通过2.2、ab+c相当于a (b+c)+优先级高于 2.3、贪心法

8、a+b 表达式与 (a+) +b 一致C 语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理的策略被称为“贪心法”。按照这个规则可能很轻松的判断a+b 表达式与 a+ +b 一致第3章、预处理3.1、注释先于预处理指令被处理#define BSC /#define BMC /*#define EMC

9、*/D),BSC my single-line commentE),BMC my multi-line comment EMCD)和E)都错误,为什么呢?因为注释先于预处理指令被处理,当这两行被展开成/或/*/时,注释已处理完毕,此时再出现/或/*/自然错误.因此,试图用宏开始或结束一段注释是不行的。3.6、内存对齐使用指令#pragma pack (n),编译器将按照 n 个字节对齐。使用指令#pragma pack (),编译器将取消自定义字节对齐方式。#include struct st1 char a ; int b ; short c ;struct st2 char a; stru

10、ct st1 b; /复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式 int c;int main(int argc, char *argv) printf(%d %dn, sizeof(struct st1), sizeof(struct st2); return 0;运行结果:12 20St1 :char占一个字节,起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。s

11、hort占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:oxxx| oooo| oo0123 4567 89 (地址)(x表示额外添加的字节)共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,这样在处理数组时可以保证每一项都边界对齐,需添加2个额外字节使结构的总大小为12 。此时的内

12、存状态为:oxxx|oooo|ooxx0123 4567 89ab (地址)到此内存对齐结束。St1占用了12个字节而非7个字节。3.7、宏参数中的#字符串中包含宏参数,那我们就可以使用“#”#define SQR(x) printf(The square of #x is %d.n, (x)*(x);再使用:SQR(8);则输出的是:The square of 8 is 64.3.8、#这个运算符把两个语言符号组合成单个语言符号。看例子:#define XNAME(n) x # n如果这样使用宏:XNAME(8)则会被展开成这样:x8#将前后两部分粘合起来第4章、指针和数组4.2、int a

13、5. sizeof(a5)关键字 sizeof 求值是在编译的时候。虽然并不存在a5这个元素,但是这里也并没有去真正访问 a5,而是仅仅根据数组元素的类型来确定其值。所以这里使用 a5并不会出错。4.3.3指针和数组的定义与声明。要确认你的代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组。如文件1中定义 char a100 = 0x31, 0x32, 0x33, 0x34, 0x35;文件2中这样进行声明extern char *a;虽然在文件 1 中,编译器知道 a 是一个数组,但是在文件 2 中,编译器并不知道这点。大多数编译器是按文

14、件分别编译的,编译器只按照本文件中声明的类型来处理。所以,虽然 a 实际大小为 100 个 byte,但是在文件 2 中,编译器认为 a 是一个char*指针,只占 4 个 byte。编译器会把存在指针变量中的任何数据当作地址来处理。故文件2中a存放的数据其实是文件1中数组a的前4个元素,即文件2中 a=0x34333231(小端机器),对这个未定义的地址进行访问,所以出错了。C语言多文件编译时,编译器不检测其声明的变量类型与定义时的类型是否匹配4.5指针的算术运算/* Compiler: GCC* Last Update: Sat 21 Apr 2012 05:25:43 PM CST*/#

15、include int main(int argc, char *argv) char a7=A,B,C,D, E, F; char (*p3)3 = &a; /编译会提示warning,运行没错,最好别这样用 char (*p4)3 = a; /编译会提示warning,运行没错,最好别这样用 int b2; printf(%dn, &b1 - &b0); /相减也是以步长来计算 printf(%sn, *p3); /指针相加就是加上指针的步长,这里是+ sizeof(char 3) printf(%sn, *(p3 + 1); printf(%cn, *p3); printf(%sn, *

16、p4); printf(%sn, *(p4 + 1); /+ sizeof(char 3) printf(%cn, *p4); return 0;运行结果:1ABCDEFDEFAABCDEFDEFA4.6.3二维数组参数与二维指针参数,二维数组初始化void fun(char a34);可以把 a34理解为一个一维数组 a3,其每个元素都是一个含有 4 个 char 类型数据的数组。上面的规则, 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。也就是说我们可以把这个函数声明改写为:void fun(char (*p)4);/* Compiler: GCC

17、* Last Update: Sat 21 Apr 2012 05:36:16 PM CST*/#include int main(int argc, char *argv) int a4=0,0,3,0,10; /第一个表示对第一行元素进行赋值,第二个对第二行. int i, j; for(i = 0; i 3; +i) for(j = 0; j 4; +j) printf(%d , aij); printf(n); return 0;运行结果:0 0 3 00 0 0 00 10 04.7.2函数指针/* Compiler: GCC* Last Update: Sat 21 Apr 201

18、2 05:45:15 PM CST*/#include int f(int a) return a;int main(int argc, char *argv) int (*ptrF1)(int) = f; int (*ptrF2)(int) = &f; printf(%x %xn, f, f + 1); /函数指针+1的结果就是该指针值直接加1,不需考虑步长 printf(%x %xn, &f, &f + 1); return 0;运行结果:80483c4 80483c580483c4 80483c5函数名是在编译时关联到某个地址.直接使用函数名的时候是指那个地址在函数名前加&还是那个地址,

19、要具体了解原理得从编译器的角度出发下面是我用gdb调试的结果,&f、f、*f、*f的值都是一样的Breakpoint 1, main (argc=1, argv=0xbffff374) at 1.c:1515 int (*ptrF2)(int) = &f;(gdb) p f$1 = int (int) 0x80483c4 (gdb) p &f$2 = (int (*)(int) 0x80483c4 (gdb) p *f$3 = int (int) 0x80483c4 (gdb) p *f$4 = int (int) 0x80483c4 4.7.3C Traps and Pitfalls书中的一

20、个例子:(* ( void (*) ( ) )0 )( )第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。第二步:(void(*) ()0,这是将 0 强制转换为函数指针类型,0 是一个地址,也就是说一个函数存在首地址为 0 的一段区域内。第三步:(*(void(*) ()0),这是取 0 地址开始的一段内存里面的内容,其内容就是保存在首地址为 0 的一段区域内的函数。第四步:(*(void(*) ()0)(),这是函数调用。第5章、内存管理5.1、野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。 “野

21、指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有两种:一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句i

22、f (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块5.2、定义数组 int a10,用memset(a, 0, sizeof(a)对其进行初始化5.3、assert是一个宏,而不是函数。如果其后面括号里的值为假,则程序终止运行,并提示出错;如果后面括号里的值为真,则继续运行后面的代码。这个宏只在 Debug 版本上起作用,而在 Release 版本被编译器完全优化掉,这样就不会影响代码的性能。、malloc()申请0字节内存man 3 mallocIf size is 0, then malloc() r

23、eturns either NULL, or a unique pointer value that can later be successfully passed to free().示例:/* Compiler: GCC* Last Update: Sat 21 Apr 2012 11:59:22 PM CST*/#include #include #include int main(int argc, char *argv) char *c = malloc(0); if(c != NULL) /申请0字节,返回不是NULL strcpy(c, aaaaaaaaaaaaaaa); puts(c); free(c); /free出错了 return 0;、void free(void *ptr);if free(ptr) has already been called before, undefined behavior occurs. If ptr is NULL, no operation is performed示例:/* Compiler: GCC* Last Update: Sun 22 Apr 2012 12:02:38 AM CST*/#include #include #in

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论