C语言深度剖析读书笔记Word文档格式.docx

上传人:b****6 文档编号:17897624 上传时间:2022-12-12 格式:DOCX 页数:14 大小:21.67KB
下载 相关 举报
C语言深度剖析读书笔记Word文档格式.docx_第1页
第1页 / 共14页
C语言深度剖析读书笔记Word文档格式.docx_第2页
第2页 / 共14页
C语言深度剖析读书笔记Word文档格式.docx_第3页
第3页 / 共14页
C语言深度剖析读书笔记Word文档格式.docx_第4页
第4页 / 共14页
C语言深度剖析读书笔记Word文档格式.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

C语言深度剖析读书笔记Word文档格式.docx

《C语言深度剖析读书笔记Word文档格式.docx》由会员分享,可在线阅读,更多相关《C语言深度剖析读书笔记Word文档格式.docx(14页珍藏版)》请在冰豆网上搜索。

C语言深度剖析读书笔记Word文档格式.docx

#defineM3//宏常量

constintN=5;

//此时并未将N 

放入内存中

......

inti=N;

//此时为N 

分配内存,以后不再分配!

intI=M;

//预编译期间进行宏替换,分配内存

intj=N;

//没有内存分配

intJ=M;

//再进行宏替换,又一次分配内存!

const 

定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,在程序运行过程中只有一份拷贝。

#define 

定义的宏常量在内存中有若干个拷贝。

宏是在预编译阶段进行替换,而const 

修饰的只读变量是在编译的时候确定其值

怎么看const修饰哪个对象

先忽略类型名(编译器解析的时候也是忽略类型名)。

看const 

离哪个近。

离谁近就修饰谁。

constint*p;

//const*p

//const 

修饰*p,p 

是指针,*p 

是指针指向的对象,不可变

intconst*p;

int*constp;

//*constp

修饰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;

//(3)语句

//(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 

编译能通过

2.2、a<

<

b+c 

相当于a<

(b+c)

+优先级高于<

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<

stdio.h>

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

************************************************************************/

chara[7]={'

A'

'

B'

C'

D'

'

E'

F'

char(*p3)[3]=&

a;

//编译会提示warning,运行没错,最好别这样用

char(*p4)[3]=a;

intb[2];

%d\n"

&

b[1]-&

b[0]);

//相减也是以步长来计算

%s\n"

*p3);

//指针相加就是加上指针的步长,这里是+sizeof(char[3])

*(p3+1));

%c\n"

**p3);

*p4);

*(p4+1));

//+sizeof(char[3])

**p4);

1

ABCDEF

DEF

A

4.6.3二维数组参数与二维指针参数,二维数组初始化

voidfun(chara[3][4]);

可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4个char类型数据的数组。

上面的规则,语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

也就是说我们可以把这个函数声明改写为:

voidfun(char(*p)[4]);

36:

16PMCST

inta[][4]={{0,0,3},{},{0,10}};

//第一个{}表示对第一行元素进行赋值,第二个{}对第二行...

inti,j;

for(i=0;

3;

++i){

for(j=0;

4;

++j){ 

%d"

a[i][j]);

}

\n"

);

0030

0000

0100

4.7.2函数指针

45:

15PMCST

intf(inta)

returna;

int(*ptrF1)(int)=f;

int(*ptrF2)(int)=&

f;

%x%x\n"

f,f+1);

/函数指针+1的结果就是该指针值直接加1,不需考虑步长

f,&

f+1);

80483c480483c5

函数名是在编译时关联到某个地址...直接使用函数名的时候是指那个地址在函数名前加&

还是那个地址,要具体了解原理得从编译器的角度出发

下面是我用gdb调试的结果,&

f、f、*f、****f的值都是一样的

Breakpoint1,main(argc=1,argv=0xbffff374)at1.c:

15

15 

(gdb)pf

$1={int(int)}0x80483c4<

f>

(gdb)p&

f

$2=(int(*)(int))0x80483c4<

(gdb)p*f

$3={int(int)}0x80483c4<

(gdb)p***f

$4={int(int)}0x80483c4<

4.7.3 

《CTrapsandPitfalls》书中的一个例子:

(*(void(*)() 

)0)()

第一步:

void(*)(),可以明白这是一个函数指针类型。

这个函数没有参数,没有返回值。

第二步:

(void(*)())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。

第三步:

(*(void(*)())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。

第四步:

(*(void(*)())0)(),这是函数调用。

第5章、内存管理

5.1、野指针,也就是指向不可用内存区域的指针。

通常对这种指针进行操作的话,将会使程序发生不可预知的错误。

“野指针”不是NULL指针,是指向“垃圾”内存的指针。

人们一般不会错用NULL指针,因为用if语句很容易判断。

但是“野指针”是很危险的,if语句对它不起作用。

野指针的成因主要有两种:

一、指针变量没有被初始化。

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。

所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。

通常会用语句if(p!

=NULL)进行防错处理。

很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块

5.2、定义数组inta[10],用memset(a,0,sizeof(a))对其进行初始化

5.3、assert 

是一个宏,而不是函数。

如果其后面括号里的值为假,则程序终止运行,并提示出错;

如果后面括号里的值为真,则继续运行后面的代码。

这个宏只在Debug版本上起作用,而在Release版本被编译器完全优化掉,这样就不会影响代码的性能。

5.3.5.3、malloc()申请0字节内存

man3malloc

Ifsizeis0,thenmalloc()returnseither 

NULL,

orauniquepointervaluethatcanlaterbesuccessfullypassedtofree().

示例:

Sat21Apr201211:

59:

22PMCST

string.h>

stdlib.h>

char*c=malloc(0);

if(c!

=NULL){//申请0字节,返回不是NULL

strcpy(c,"

aaaaaaaaaaaaaaa"

puts(c);

free(c);

//free出错了

5.3.5.4、voidfree(void*ptr);

iffree(ptr)hasalreadybeencalledbefore,undefinedbehavioroccurs. 

IfptrisNULL,nooperationisperformed

Sun22Apr201212:

02:

38AMCST

char*c=malloc(20*sizeof(char));

=NULL){

//free(c);

//运行出错:

doublefreeorcorruption

c=NULL;

第6章、函数

6.1、修改别人代码的时候不要轻易删除别人的代码,应该用适当的注释方式,

while(condition)

statement1;

//////////////////////////////////////

//yourname,2008/01/07delete

//if(condition)

//{

//for(condition)

//{

//

Statement2;

//}

//}

//else

statement3;

////////////////////////////////////////

///////////////////////////////////////

//yourname,2000/01/07add

...

newcode

statement4

6.4.2、不使用任何变量编写strlen函数

/**********************************************************

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > IT计算机 > 计算机软件及应用

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1