废话不多说,请看以下代码:
#define buffer ((char *) *( (int far *)0x200 ))
main(){
buffer=(char *)malloc(20);
buffer[10]=0;
while(buffer[10]!=8){
buffer[buffer[10]]='a' + buffer[10];
buffer[10]++;
}
free(buffer);
}
我们不妨忽略#define buffer ...这个语句,把buffer看做b代码如下:
main(){
b=(char *)malloc(20);
b[10]=0;
while(b[10]!=8){
b[b[10]]='a' + b[10];
b[10]++;
}
free(b);
}
b指向了刚刚开辟的20个字节大小的内存首地址,且b移动的单元为1个字节(即char类型单元大小)
b[10]=0意思是由b指向的首地址向后移动10*1(b的移动单元)个单位到达的地址,也就是第11个元素所在内存的首地址
根据n[10]=0;b[10]!=8和循环中的b[10]++;可知该循环循环8次
b[b[10]]='a'+b[10];这句什么意思?
循环第一次:b[0]='a'+0
循环第二次:b[1]='a'+1
循环第八次:b[7]='a'+7
我们枚举下,可知原来是把a~h八个字符分别装到b[0]~b[7]中
free(b);释放b所指向的地址,可能问题来了?free如何知道b所指向的空间有20B大小呢?
答:存放在b所指空间大小存放在寄存器ax中,并压入栈ss中保存。
(寄存器:cpu中可以储存数据的器件)
举个栗子附图:
我们写的c代码如下:
我们使用系统自带的debug调试一次这个程序
首先,已知main在我的虚拟机中的偏移地址为01fa,这样我们直接跳至cs:1fa处,
找到main函数中转化成汇编代码的c语言代码。
SUB SP,+02 (汇编指令)等价于 sp+=2; 想必大家已经猜出来了,这正是char *b;其含义就是初始化一个空间给b(姑且可以这样抽象的理解),而这个空间将放在栈(ss:sp注:括号中不理解没关系,ss<<2+sp代表的是栈的物理首地址)中,sp是指向栈顶的,所以其+2(为什么+2?因为b是一个指针,指针大小是2Byte,栈存储单位是1B,所以指针入栈栈顶需要+2,姑且可以这样抽象的理解)
之后,MOV AX,0014 (汇编指令)作用等价于AX=20;这里的0014是16进制,转化为10进制就是20,即我们malloc的空间大小。
PUSH AX (汇编指令)翻译过来就是ax的值入栈,把ax的值保存在栈中,就是保存malloc分配空间size的值。
于是乎,我们证明了free是如何知道malloc所分配的空间大小。ax=20;ax入栈;到运行free时在从栈中取出20即可。
简而言之,这个简化的程序就是把a~f8个字符写入申请的b[0]~b[7]中
那么,重点来了(注意!这里才是重点)
define buffer ((char *) *( (int far *)0x200 ))
这里第一反应就是:Are you kidding me?
你会说为啥变量变成一个数了呢?还是16进制的数。
答:首先,*( (int far *)0x200 )
far:指明后面的对象是一个确切的物理地址(就是根据0x200能直接在内存中找到是0x200的物理地址)
第一个* :代表后面的对象是一个内存空间
第二个* :代表后面的对象是一个内存空间地址
(int far *)0x200:表示这个一个物理地址,该地址的存储单位是2Byte(int即2Byte,但有些操作系统不同,这里不多做解释)
*(int far *)0x200:表示0x200这个物理地址的内存空间
(char *) *( (int far *)0x200 ):char * 纯属修饰的是内存空间中的数据,表示这个数据是一个char 型指针,(以下可以跳过)
本质是:这个数据是一个地址,一个单位为1Byte的空间的地址。也就是0x200地址空间中装着一个单位为1Byte的地址,这个地址我们还未确定,因为我们还未初始化。
b=(char *)malloc(20);初始化0x200地址内存,即把malloc开辟的首地址装入0x200中
b即装在0x200地址内存中的数据。
b[10]:装在0x200地址内存中的数据是一个地址,b[10]即这个地址+10Byte(char)地址内存中的数据。
这里来波实战:
首先,我们写一个c代码,这里的编译器用的是古老的TC,为什么要用这么老的东西?
因为越古老的东西越不存在那些华丽而又繁杂的装饰,资源分配,链接,预编译特别简单,便于我们分析,我们甚至可以自己动手写一个c语言编译器开发环境。
之后,我们运行完这个程序,查看0x200地址的内存,(图中0000:0200代表0x200地址)
发现内存数据为00 00 ,根据我们前面的推测,内存数据00 00 是一个地址,是malloc分配空间的首地址。
为什么只取前两个?
malloc此类的函数分配的空间都是在栈中,所以,对应的前两个字节应该是栈的偏移地址。
于是,我们来到地址00 00 处:
ds:0000可以抽象理解为查看0000地址处的内存。
这里前面是以16进制表示,后面表示的是16进制转化为ascll的值
我们可以看到a~h的ascll码十六进制和其转化为字符形式,从b~(b+7)地址内存中依次存储(这里b的地址即00 00)
于是,我们证明了0x200中存的数据是一个地址,且这个地址是malloc的首地址。
最后留一个小彩蛋,那么b[10]在哪呢?其中的值又是多少?仔细看看00 00地址内存的那张图,你将会得到答案!