重新学习c语言

调程序调的我“心蛇狂舞”,恩,如果dota打输了想砸显示器的话,这会儿我想把服务器砸了。。算了,不说了,程序仍一边,来写篇博客放松下。

前几天碰到几个问题,使我对c语言的认识又深刻了一步,惭愧惭愧,嘻嘻

char buf[5],msg[5];
int num=12345;
sprintf(buf,"%d",num);
printf("%sn",buf);
sprintf(msg,"%d",num);
printf("%sn",msg);
printf("%sn",buf);

看这个代码,你能说出这个代码的输出吗?

 正确的输出应该是

12345
12345
                                                                .

第三行代码输出为空,这是为什么呢,第一眼你会发现缓冲区溢出,于是你把buf和msg数组扩大,于是问题解决了。看起来了很完美,可是我的内心仍然充满了疑惑,溢出时发生了什么导致buf为空了?
我们先来看一张x86一个进程的内存布局

memory

如图所示,我们知道所有的局部变量都是存放在栈里,而x86架构下,栈的增长又自上而下的,同时也就自高地址到低地址的增长,我们程序里先申请buf,后申请msg,那么buf在栈里就位于msg的上方,而msg的地址要小于buf的地址。

我们把这个栈向右旋转90度,

ABCDEabcde*

用ABCDE比较msg占的内存,abcde表示buf占的内存,从左到右是从低地址到高地址。
那么我们第一次往buf里写东西时,由于buf只有abcde5个位置,而写进去整个字符串需要6个位置,于是buf缓冲区溢出,将占用e后面的*的位置。而当往msg里面写字符串时,同样由于位置的不够,会占用E后面a的位置。由于字符串最后的字符为0,所以往a里面也写入了0,进而当我们第三次打印buf时,由于buf的首地址里存放的0,所以认为buf是空字符串,也就什么都没有打印。

下面看第二个代码,这个两个输为什么一样

int num=35;
printf("%s\n",(char*)&num);
printf("%c\n",*(char*)&num);

第二个输出很直观,num是一个整形,在内存里占用4个字节,&符号取得一个int的地址,然后强制转化成一个char*的地址,也就是1个字节,即num的第一个字节的地址(如果获得第二个字节的地址可以(char*)&num+1),然后加个*号,则输出第一个地址里的内容,我电脑上输出是#,因为#对应的ASCII码是35。(我的电脑是intel处理器,intel处理器使用小端LSB,所以num的第一个字节存储了35,如果是大端,则在最后一个字节存储35)

理解第一个输出需要知道printf函数接受的参数,其实就是相应的首地址,这里%s表示接收一个字符串,那么printf第二个参数就是接收这个字符串的首地址,从首地址开始打印。由于num在内存里是35-00-00-00,所以打印了第一个字符。可以想象一下,如果是大端的话,什么也不会输出。




comments powered by Disqus