原创C语言高级用法学习笔记.docx
- 文档编号:17643674
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:31
- 大小:96.73KB
原创C语言高级用法学习笔记.docx
《原创C语言高级用法学习笔记.docx》由会员分享,可在线阅读,更多相关《原创C语言高级用法学习笔记.docx(31页珍藏版)》请在冰点文库上搜索。
原创C语言高级用法学习笔记
1.#的作用
#是将其后的变量直接转换为字符串
voidWriteLog(arg)
{
printf("%s=%d",#arg,arg)
}
2.##的作用
##的作用是连接两个变量
如
#definecombine_converse_str(x,y)x##y
intErr;
intNum;
printf("%s",combine_converse_str(Err,Num));
结果为:
ErrNum
再如:
inta=1;
intb=2;
intab=3;
printf("%d",a##b);
结果为:
3
后面那句相当于printf("%d",ab);
3.do{}while(0)作用
1,空的宏定义避免warning:
#definefoo()do{}while(0)
2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。
3,用在宏定义上并且当宏定义需要定义成多条语句的组合时,可以保证这几条语句是一个整体的,并且可以消除使用这个宏定义后添加";"所带来的报错,例如:
#defineaaa(x,y)\
do{\
inta=0;\
intb=0;\
函数1(x);\
函数2(y);\
}while(0)
调用aaa(x,y);时不会报错并且如果使用:
if(m)
aaa(1,2);
else
aaa(3,4);
时运行也不会使某些语句没有被运行到。
4.数组名的使用
A.数组名作为函数参数:
数组名用在某个函数A的参数中时,处于函数传递效率原因,会被强制转换成了指针,此后在函数A就完全是一个值等于对应数组首地址的指针变量,可自加自减等
注意!
只有在作为函数参数的情况下才会将数组名强制转换成指针,其他的都不会转换。
另:
数组名作为函数参数传递时,函数声明的写法有多种
intaaa(charx[])
intaaa(charx[1])
intaaa(charx[100])
intaaa(char*x)
作为函数参数时的参数声明也和普通的数组定义一样要合法,比如不能intaaa(charx[0]),因为定义一个数组也不能用charx[0]来定义,编译器在检查参数的声明合法后,如果发现参数是数组,就会强制转换成指针,所以intaaa(charx[1])和intaaa(charx[100])的结果是一样的。
B.数组名用在sizeof上:
结果返回的是整个数组所占空间的大小,而不是一个指针的长度
C.数组名与&和*:
a与&a与&a[0]的值都相等,a是数组名,&a是整个数组地址,&a[0]是数组首元素的地址,他们都相等
inta[5]={1,2,3,4,5};
printf("a=%x\n",a);
printf("&a=%x\n",&a);
printf("*&a=%x\n",*&a);
printf("&a[0]=%x\n",&a[0]);
printf("*&a[0]=%x\n",*&a[0]);
printf("*(&a[0])=%x\n",*(&a[0]));
printf("*((int*)(&a))=%x\n",*((int*)(&a)));
printf("*(*(&a))=%x\n",*(*(&a)));
结果:
a=12ff34
&a=12ff34
*&a=12ff34---*与&消掉,*&a=a=0x12ff34
&a[0]=12ff34
*&a[0]=1---*与&消掉,*&a[0]=a[0]=1
*(&a[0])=1---*与&消掉,*(&a[0])=*&a[0]=a[0]=1
*((int*)(&a))=1---*与&之间有个强制类型转换(int*),无法直接消掉,所以按照括号次序来运算(int*)(&a)=0x12ff34,*((int*)(&a))=*(0x12ff34)=1
*(*(&a))=1---*与&消掉,*(*(&a))=*a=1
D.指针数组:
定义:
基类型 *数组名[]
使用:
像普通的数组一样使用,数组名代表首地址,数组名[0]表示第一个元素的容,以此类推。
常见的是字符串数组,例如:
char *Names[]=
{
Bill,
Sam,
Jim
};
实际存中保存的就是
即首地址0x12ff0c{0x00423018,0x0042f2f4,0042201c},其中0x00423018地址所保存的就是”Bill”,所以数组名Name=0x12ff0c,&Name=0x12ff0c,*Name=0x00423018,**Name=0x42
printf("Name=%x\n",Name);
printf("&Name=%x\n",&Name);
printf("*&Name=%x\n",*&Name);
printf("&Name[0]=%x\n",&Name[0]);
printf("*Name=%x\n",*Name);
printf("**Name=%x\n",**Name);
printf("Name[0]=%x\n",Name[0]);
printf("*Name[0]=%x\n",*Name[0]);
printf("*&Name[0]=%x\n",*&Name[0]);
printf("*(&Name[0])=%x\n",*(&Name[0]));
printf("(*((int*)(&Name)))=%x\n",(*((int*)(&Name))));
printf("(*(*(&Name)))=%x\n",(*(*(&Name))));
结果是
Name=&Name=*&Name=&Name[0]=0x12ff0c
*Name=Name[0]=*&Name[0]=*(&Name[0])=(*((int*)(&Name)))=(*(*(&Name)))=0x00423018
**Name=*Name[0]=0x42
注意!
指针是一级,数组是又一级,所以指针数组是二级指针,定义对应的指针变量要用二级指针,比如int**p=Name;不能直接用int*p=Name
5.常量的定义和指针常量的定义及使用:
1.常量的定义:
constinta=10;//在其他地方不允许修改a的值,否则会编译不通过.
也可以写成intconsta=10;//一样的
2.指针常量的定义和使用:
有3种定义:
constint*pi;和intconst*pi;以及int*constpi;
1)前两种情况一样的,没有区别。
1) 如果const 修饰在*pi前(前2种情况)则不能改的是*pi(即不能 类似这样:
*pi=50;赋值)而不是指pi.
2) 如果const 是直接写在pi前(后一种情况)则pi不能改(即不能类似 这样:
pi=&i;赋值)。
6.函数参数传递的4种类型:
1.值传递
voidExchg1(intx,inty)
{
inttmp;
tmp=x;
x=y;
y=tmp;
printf("x=%d,y=%d\n",x,y);
}
main()
{
inta=4,b=6;
Exchg1(a,b);
printf("a=%d,b=%d\n",a,b);
return(0);
}
2.地址传递
voidExchg2(int*px,int*py)
{
inttmp=*px;
*px=*py;
*py=tmp;
printf("*px=%d,*py=%d.\n",*px,*py);
}
main()
{
inta=4;
intb=6;
Exchg2(&a,&b);
printf("a=%d,b=%d.\n",a,b);
return(0);
}
3.引用传递
voidExchg3(int&x,int&y)
{
inttmp=x;
x=y;
y=tmp;
printf("x=%d,y=%d\n",x,y);
}
main()
{
inta=4;
intb=6;
Exchg3(a,b);
printf("a=%d,b=%d\n",a,b);
return(0);
}
4.变长参数传递:
使用省略号…指定参数表
使用的宏及其定义(x86中):
#defineva_start_crt_va_start
#defineva_arg_crt_va_arg
#defineva_end_crt_va_end
typedefchar*va_list;
#define_ADDRESSOF(v)(&(v))
#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
#define_crt_va_start(ap,v)(ap=(va_list)_ADDRESSOF(v)+_INTSIZEOF(v))
#define_crt_va_arg(ap,t)(*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))
#define_crt_va_end(ap)(ap=(va_list)0)
实例:
#include"stdafx.h"
#include"stdio.h"
#include"string.h"
#include"stdarg.h"
/*至少需要一个确定的参数,注意括号的省略号*/
intdemo(char*fmt,...)
{
va_listargp;
intargno=0;
char*para;
va_start(argp,fmt);
while
(1)
{
para=va_arg(argp,char*);
if(strcmp(para,"")==0)
break;
printf("Parameter#%dis:
%s\n",argno,para);
argno++;
}
va_end(argp);
return0;
}
intmain()
{
demo("DEMO1","This","is","a","demo!
","");
return0;
}
运行结果:
详解:
1.函数的参数和局部变量都是存储在栈里
2.栈的存分配是按从高地址到低地址分配的(而其他的正常是从小到大分配,比如全局变量定义等),即先分配高地址的,再分配低地址的存
3.所有的函数参数(不管是定长参数还是变长参数)入栈都是从右到左入栈,如调函数a(x,y,z),则z先入栈,并存在栈底(高地址),其次y,其次x。
4.数据在存里的存储默认都按照字节对齐来存储(x86按照4字节对齐),如:
intaaa(chara,charb,charc)
{
return(int)(a+b+c);
}
调用函数aaa(5,6,7);时,存中是这样的:
所以,如果是数值,则会直接存储在栈中,如果是指针类型(如字符串),则会把指针值(即字符串首地址的值)存储在栈中,从右到左依次按照从高地址到低地址存储。
如实例的程序调用demo("DEMO1","This","is","a","demo!
","");得到的堆栈为:
即0x0012FEFb开始往低地址方向的部分为此函数的栈空间,存入的值分别为0x00422038,0x00422058,0x0042204C,0x00422034,0x00422044,0x0042203C,这6个地址分别所指向的实际上就是静态存储区存储””,"demo!
","a","is","This","DEMO1"的地方,见图
另:
如果改成demo("DEMO1",1,2,"a","demo!
","");则其中的数字1,2直接保存在栈中,见图
5.#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
的运算结果实际上就是当1<=sizeof(n)<=4时取4,5<=sizeof(n)<=8时取8……即4的倍数
其结果实际上就是一个参数所占的空间大小,所以每次调用va_arg取参数时,va_arg里只需移动_INTSIZEOF(n)长度就对了。
6.所以实际上变长参数的实现原理很简单,不过要注意一点,就是由于没有哪个地方表明栈中保存这些参数的长度是多少,所以其实调用va_arg逐个取里面的参数时,是不知道取多少的,所以需要:
1).从demo(n,…)的第一个已知参数n传入,例如demo(4,1,2,3,4)
2).或者从demo(string,…)的string中利用格式化字符串来间接标识后面有多少个参数,例如demo(“test1=%s,test2=%d”,str,i)通过判断%的个数来得到要调用2遍va_arg(其实这种有种简便的方法就是调用va_start后直接使用sprintf或者vsprintf或者vnsprintf来打印格式化的字符串)
3).或者通过实例中那样最后一个用\0来标识
4).或者还有其他的方法,总之需要有所标识
然后再在函数里做相应判断,使va_arg的调用次数正确。
总说明:
1.地址传递的实质也是值传递,只是通过地址来可以对实参进行操作;
2.引用传递就是在定义函数的时候各参数加一个&取地址符号,其他的都跟值传递一样使用,同样可以改变实参变量的容。
引用传递实际上跟地址传递过程是一样的,只是函数压栈的时候由于函数(以引用参数部分的实例为例)的参数定义为voidExchg3(int&x,int&y),所以就告诉编译器压栈是要压调用者Exchg3(a,b);实参a,b的地址,即&a和&b,而在voidExchg3(int&x,int&y)函数部使用的时候是用x和y,即*(&x)和*(&y),所以其实是和地址传递完全一样的,只是换了一种表达形式而已,编译成汇编语言后是一模一样的。
3.函数参数传递过程是在一个新的栈空间中对实参进行压栈,然后在函数部读取这些栈的数据的过程,只不过如果在函数定义时参数是数值型的话(例如值传递的intaaa(inta,intb,intc))就将这个参数的值压栈,如果在函数定义时参数是地址型的话(例如地址传递intaaa(int*a,int*b)和引用传递intaaa(int&a,int&b))就将地址值压栈。
所谓在新的栈空间中压栈实质就是把当前需要压栈的数值或地址按照一定次序拷贝到新的栈空间中。
附:
函数压栈过程和汇编程序对应解析
传值方式C语言代码:
voidchangefunc(chara,charb)
{
chartemp;
temp=a;
a=b;
b=temp;
}
intmain()
{
charx=5,y=6;
changefunc(x,y);
demo("DEMO1",1,2,"a","demo!
","");
return0;
}
编译成汇编对应的代码:
8:
{
0040D880pushebp---步骤5.将ebp的值0x0012ff48压栈,此语句执行后esp=0x0012fee4=M-0x10,ebp=0x0012ff48
0040D881movebp,esp---步骤6.此语句执行后esp=0x0012fee4=M-0x10,ebp=0x0012fee4
0040D883subesp,44h---步骤7.此语句执行后esp=0x0012fea0=M-0x54,ebp=0x0012fee444h为编译器预留的栈空间,提供函数申请局部变量等使用(这个值的大小编译器会自动根据实际函数使用空间来定,无需上层干涉)
0040D886pushebx---步骤8.此语句执行后esp=0x0012fe9c=M-0x58,ebp=0x0012fee4
0040D887pushesi---步骤9.此语句执行后esp=0x0012fe98=M-0x5c,ebp=0x0012fee4
0040D888pushedi---步骤10.此语句执行后esp=0x0012fe94=M-0x60,ebp=0x0012fee4
0040D889leaedi,[ebp-44h]
0040D88Cmovecx,11h
0040D891moveax,0CCCCCCCCh
0040D896repstosdwordptr[edi]
9:
chartemp;
10:
temp=a;
0040D898moval,byteptr[ebp+8]
0040D89Bmovbyteptr[ebp-4],al
11:
a=b;
0040D89Emovcl,byteptr[ebp+0Ch]
0040D8A1movbyteptr[ebp+8],cl
12:
b=temp;
0040D8A4movdl,byteptr[ebp-4]
0040D8A7movbyteptr[ebp+0Ch],dl
13:
}
0040D8AApopedi---步骤11.此语句执行后esp=0x0012fe98=M-0x5c,ebp=0x0012fee4
0040D8ABpopesi---步骤12.此语句执行后esp=0x0012fe9c=M-0x58,ebp=0x0012fee4
0040D8ACpopebx---步骤13.此语句执行后esp=0x0012fea0=M-0x54,ebp=0x0012fee4
0040D8ADmovesp,ebp---步骤14.此语句执行后esp=0x0012fee4=M-0x10,ebp=0x0012fee4
0040D8AFpopebp---步骤15.此语句执行后esp=0x0012fee8=M-0xc,ebp=0x0012ff48
0040D8B0ret---步骤16.此语句执行后esp=0x0012feec=M-0x8,ebp=0x0012ff48
33:
intmain()
34:
{
004010F0pushebp
004010F1movebp,esp
004010F3subesp,48h
004010F6pushebx
004010F7pushesi
004010F8pushedi
004010F9leaedi,[ebp-48h]
004010FCmovecx,12h
00401101moveax,0CCCCCCCCh
00401106repstosdwordptr[edi]
35:
charx=5,y=6;
00401108movbyteptr[ebp-4],5
0040110Cmovbyteptr[ebp-8],6
36:
changefunc(x,y);---步骤1.此时esp=0x0012fef4,ebp=0x0012ff48记0x0012fef4值为M
00401110moval,byteptr[ebp-8]
00401113pusheax---步骤2.将y的值6压栈,此语句执行后esp=0x0012fef0=M-4,ebp=0x0012ff48
00401114movcl,byteptr[ebp-4]
00401117pushecx---步骤3.将x的值5压栈,此语句执行后esp=0x0012feec=M-8,ebp=0x0012ff48
00401118callILT+65(changefunc)(00401046)---步骤4.行00401046是一条跳转语句jmpchangefunc(0040D880),压入返回地址,所以跳转后esp=0x0012fee8=M-0xc,ebp=0x0012ff48
0040111Daddesp,8---步骤17.因为callchangefunc函数之前有2个参数压栈,所以要恢复此前的esp,就需要加上2*4,此语句执行后esp=0x0012fef4=M,ebp=0x0012ff48
37:
demo("DEMO1",1,2,"a","demo!
","");
00401120pushoffsetstring""(00422038)
00401125pushoffsetstring"demo!
"(00422058)
0040112Apushoffsetstring"This"(00422044)
0040112Fpush2
00401131push1
00401133pushoffsetstring"DEMO1"(0042203c)
00401138callILT+5(demo)(0040100a)
0040113Daddesp,18h
38:
return0;
00401140xoreax,eax
39:
}
00401142popedi
00401143popesi
00401144popebx
00401145addesp,48h
00401148cmpebp,esp
0040114Acall__chkesp(00401270)
0040114Fmovesp,ebp
00401151popebp
00401152ret
7.EBP和ESP
EBPExtendbasepointer:
用于存取栈的指针
ESPExtendstackpointer:
栈顶指针
ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针
地址按照从小到大排列,压栈都是从高地址从低地址压栈,
低地址
高地址
按运行顺序前一个函数压的栈
栈顶esp
某时刻临时保存的栈指针ebp
8.多个字符串可以直接连在一起写
如:
#definePRINT(NAME)printf("to""ken"#NAME"=%d\n",NAME)
a=1;PRINT(a);的结果就是tokena=1;
9.大小端的记忆口诀
小弟小,大弟大。
即小端低位在小的地址,大端低位在大的地址。
一般x86使用小端,powerpc使用大端,ARM可以设置使用大端还是小端
10.函数名的使用
函数名可以理解为
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 原创 语言 高级 用法 学习 笔记