曲径通幽处——记录MemCpy调用过程
刺猬@http://blog.csdn.net/littlehedgehog
关于MemCpy C语言的调用问题,我在于渊的博客中也看到有读者提出过类似的问题。当时也没想明白,和天之痕仔细探讨了下,把得出的结论种种罗列下来.
MemCpy声明: 摘自《自己动手》 P185
PUBLIC void * memcpy(void *pDst,void *pSrc,int iSize);
本来是个很普通的C函数声明,没多少可讨论的价值,但坏就坏在函数的调用上,不知会让多少大一当年自认为C语言不过尔尔的年轻人大跌眼镜。
memcpy( &gdt, (void*)(*((t_32*)(&gdt_ptr[2]))), *((t_16*)(&gdt_ptr[0]))+1 ) ;
我们来一个一个地说明。
第一个参数 &gdt
gdt的定义是一个数组,如下所示:
PUBLIC DESCRIPTOR gdt[GDT_SIZE];
可能有人会想gdt既然是数组名,那么gdt就是指向数组首地址的指针了,而&gdt 就是指向前一个指针的指针。那么这个实参就应该是一个二级指针了吧~~ 如果这样认为就错了。我们不妨做这样一个实验:
#include <stdio.h>
int main()
...{
int a[3]=...{0,1,2};
printf("%d %d ",a,&a);
return 0;
}
把结果打印出来看看吧,可以看到其实两个a和&a打印出来的数值是一样的! 也就是说&a也是数组首地址。我们的第一个结论是&gdt 还是gdt描述符数组的首地址!
第二个参数: (void*)(*((t_32*)(&gdt_ptr[2])))
初看很麻烦,其实我们挨着把它剥离出来分析就觉得很简单了。
最里面一层是&gdt_ptr[2] ,这个还好理解,就是取gdt_ptr第三个元素的地址,然后我们在其前面加上一个(t_32 *),很抽象。如果我们换一个样子这个东西就不抽象了。
(int *)(&a[2]) 这个就好懂了吧~~ 把数组a的第三个元素取出地址然后强制转化为int类型的指针。
(t_32 *)(&gdt_ptr[2]) 把gdt_ptr数组第三个元素取出地址然后强制转化为32位的指针。 但是我们为什么要这么做呢,因为GDT的基址是32位的,我们可以理解为 取第三个元素(gdt_ptr[2])的地址即是找到了一个指向第三个元素的指针,但是这个指针只是指向了第三个元素这个小块内存,而我们要GDT的基址是4小块内存(基址是32位的哦!),所以我们强制加上一个(t_32 *),让它指向4块内存。
好了,再到外面一层,我们又加上了一个*,这个应该很好理解的,一个地址又加上了*,表示是这个内存地址里面的值。如同
int a=3;
int b=*(&a)
到最后一层了,我们再次把问题简化,既然前面提到内存地址又加上一个* 就是该地址里面的值,那么 上面的表达式我们改写成这个 (void *)a 试问这个表示何物?
我们做如下实验:
int a=3;
void *p=(void *)a;
printf("%d ",*p);
打印出来的结果是多少? 是3么?还是打印的是a的地址?这个问题不回答可能反而是对的。因为上面的代码是错误的! 因为p的指向是个不正确的值,我们运行就会报错! 正确的代码如下:
int a=3;
void *p=(void *)a;
printf("%d ",p);
这个打印结果是3!我当时纳闷了很久,这个为什么是3? p的值是3,也就是说p指向地址3的那个内存单元。我们回头看看这句 void *p=(void *)a; 按照我们普通的理解 int b=a; 把a的值拷贝到b ,那么void *p=(void *)a; 应该就是把a的值拷贝到了p,但是由于p变量的特殊性,它是指针,p得到的值是3,这表示p指向地址3的内存单元。 按照那么第二个参数的意思也不言而喻了:
第二个结论:
(void*)(*((t_32*)(&gdt_ptr[2]))) 先找到原来的gdt的基址(也就是 &gdt_ptr[2] ),为了取得后面四小块内容,我们由此引出一个强制转化为t_32的指针,该指针的值是原gdt的值,也就是指向原基址,很简单的,人为地复杂化了~~
另外,书中的第三个参数是错误的,gdt这个数据结构的大小应该是 界限+1 ,而不是直接的gdt界限 *((t_16 *)(&gdt_ptr[0]))
同时建议各位看官有时间把这个函数调用反汇编看看,可能理解更深入点儿
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/littlehedgehog/archive/2008/03/20/2200212.aspx