C语言深度剖析读书笔记.docx
- 文档编号:3182252
- 上传时间:2022-11-19
- 格式:DOCX
- 页数:14
- 大小:22.63KB
C语言深度剖析读书笔记.docx
《C语言深度剖析读书笔记.docx》由会员分享,可在线阅读,更多相关《C语言深度剖析读书笔记.docx(14页珍藏版)》请在冰豆网上搜索。
C语言深度剖析读书笔记
C语言深度剖析读书笔记
第1章关键字
1.1、定义与声明的区别:
定义创建了对象并为对象分配了内存,声明没有分配内存
1.2、register请求编译器尽可能将变量存在CPU寄存器中以提高访问速度,register变量必须为CPU寄存器所能接受的类型,它须是一个单一的值,并且长度<=整型的长度,由于register变量可能不放在内存中,故不可以用”&”来获取它的地址
1.3、函数前面加static使得函数成为静态函数,它的作用域仅限于本文件中,故又称内部函数
1.4、case关键字后面只能是整数或字符型的常量或常量表达式。
constinta=5;
casea:
//const只读变量,编译出错,caselabeldoesnotreducetoanintegerconstant
case1.1:
//小数,编译出错,caselabeldoesnotreducetoanintegerconstant
case3/2:
//没有问题,分数会被转换成整数
1.5、“跨循环层”的概念本身是说,由外层循环进入内层循环是要重新初始化循环计数器的,包括保存外层循环的计数器和加载内层循环计数器,退出内层的时候再恢复外层循环计数器。
把长循环放在里面可以显著减小这些操作的数量,还可以增加cache的命中率。
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少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 只读变量分配存储空间,而是将它们保存在符号表中,这使
得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
例如:
#defineM3//宏常量
constintN=5;//此时并未将N 放入内存中
......
inti=N;//此时为N 分配内存,以后不再分配!
intI=M;//预编译期间进行宏替换,分配内存
intj=N;//没有内存分配
intJ=M;//再进行宏替换,又一次分配内存!
const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,在程序运行过程中只有一份拷贝。
#define 定义的宏常量在内存中有若干个拷贝。
#define 宏是在预编译阶段进行替换,而const 修饰的只读变量是在编译的时候确定其值
怎么看const修饰哪个对象
先忽略类型名(编译器解析的时候也是忽略类型名)。
看const 离哪个近。
离谁近就修饰谁。
constint*p;//const*p
//const 修饰*p,p 是指针,*p 是指针指向的对象,不可变
intconst*p;//const*p
//const 修饰*p,p 是指针,*p 是指针指向的对象,不可变
int*constp;//*constp
//const 修饰p,p 不可变,p 指向的对象可变
constint*constp;//前一个const 修饰*p,后一个const 修饰p,指针p 和p 指向的对象
都不可变
1.8、volatile
编译器遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
先看看下面的例子:
inti=10;
intj=i;//
(1)语句
intk=i;//
(2)语句
这时候编译器对代码进行优化,因为在
(1)
(2)两条语句中,i 没有被用作左值。
这时候
编译器认为i 的值没有发生改变,所以在
(1)语句时从内存中取出i 的值赋给j 之后,这个
值并没有被丢掉,而是在
(2)语句时继续用这个值给k 赋值。
编译器不会生成出汇编代码
重新从内存里取i 的值,这样提高了效率。
但要注意:
(1)
(2)语句之间i 没有被用作左值才行。
再看另一个例子:
volatileinti=10;
intj=i;//(3)语句
intk=i;//(4)语句
volatile 关键字告诉编译器i 是随时可能发生变化的,
每次使用它的时候必须从内存中取出i
的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。
这样看来,如果i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数
据,就容易出错,所以说volatile 可以保证对特殊地址的稳定访问。
1.9 、大部分编译器中,默认情况,enum会转化为int
enumColor
{
GREEN=1,
RED
}Col
故sizeof(Col)=sizeof(int)
第2章、符号
2.1、注释
int/*...*/i; //编译器会用空格代替原来的注释,这里相当于int i 编译能通过
2.2、a<
+优先级高于<<
2.3、贪心法 a+++b表达式与(a++)+b一致
C语言有这样一个规则:
每一个符号应该包含尽可能多的字符。
也就是说,编译器将程
序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,
那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组
成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串
已不再可能组成一个有意义的符号。
这个处理的策略被称为“贪心法”。
按照这个规则可能很轻松的判断 a+++b表达式与a+++b一致
第3章、预处理
3.1、注释先于预处理指令被处理
#defineBSC//
#defineBMC/*
#defineEMC*/
D),BSCmysingle-linecomment
E),BMCmymulti-linecommentEMC
D)和E)都错误,为什么呢?
因为注释先于预处理指令被处理,当这两行被展开成//…或
/*…*/时,注释已处理完毕,此时再出现//…或/*…*/自然错误.因此,试图用宏开始或结束一段
注释是不行的。
3.6、内存对齐
使用指令#pragmapack(n),编译器将按照n个字节对齐。
使用指令#pragmapack(),编译器将取消自定义字节对齐方式。
#include
structst1
{
chara;
int b;
shortc;
};
structst2
{
chara;
structst1b; //复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式
intc;
};
intmain(intargc,char**argv)
{
printf("%d%d\n",sizeof(structst1),sizeof(structst2));
return0;
}
运行结果:
1220
St1:
char占一个字节,起始偏移为0,int占4个字节,min(#pragmapack()指定的数,这个数据成员的自身长度)=4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。
short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。
到此规则1的数据成员对齐结束,此时的内存状态为:
oxxx| oooo| oo
0123 4567 89(地址)
(x表示额外添加的字节)
共占10个字节。
还要继续进行结构本身的对齐,对齐将按照#pragmapack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragmapack指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,这样在处理数组时可以保证每一项都边界对齐,需添加2个额外字节使结构的总大小为12。
此时的内存状态为:
oxxx|oooo|ooxx
0123456789ab (地址)
到此内存对齐结束。
St1占用了12个字节而非7个字节。
3.7、宏参数中的#
字符串中包含宏参数,那我们就可以使用“#”
#defineSQR(x)printf("Thesquareof"#x"is%d.\n",((x)*(x)));
再使用:
SQR(8);
则输出的是:
Thesquareof8is64.
3.8、##这个运算符把两个语言符号组合成单个语言符号。
看例子:
#defineXNAME(n)x##n
如果这样使用宏:
XNAME(8)
则会被展开成这样:
x8
##将前后两部分粘合起来
第4章、指针和数组
4.2、inta[5].sizeof(a[5])关键字sizeof求值是在编译的时候。
虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值。
所以这里使用a[5]并不会出错。
4.3.3指针和数组的定义与声明。
要确认你的代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组。
如文件1中定义chara[100]={0x31,0x32,0x33,0x34,0x35};
文件2中这样进行声明externchar*a;
虽然在文件1中,编译器知道a是一个数组,但是在文件2中,编译器并不知道这点。
大多数编译器是按文件分别编译的,编译器只按照本文件中声明的类型来处理。
所以,虽然a实际大小为100个byte,但是在文件2中,编译器认为a是一个char*指针,只占4个byte。
编译器会把存在指针变量中的任何数据当作地址来处理。
故文件2中a存放的数据其实是文件1中数组a的前4个元素,即文件2中a=0x34333231(小端机器),对这个未定义的地址进行访问,所以出错了。
C语言多文件编译时,编译器不检测其声明的变量类型与定义时的类型是否匹配
4.5指针的算术运算
/**********************************************************************
*Compiler:
GCC
*LastUpdate:
Sat21Apr201205:
25:
43PMCST
************************************************************************/
#include
intmain(intargc,char**argv)
{
char
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 深度 剖析 读书笔记
![提示](https://static.bdocx.com/images/bang_tan.gif)