C指针通过编译器实现汇编语言类似内存间接寻址功能,编程语言的差异主要来自编译器的解释

C指针通过编译器实现汇编类似内存间接寻址功能,

编程语言的差异主要来自编译器的解释吧。

担心图片久了失效,我都重新转存的哈,希望造福对此感兴趣的同学们。。

参考URL:

http://bbs.pediy.com/showthread.php?t=105674

http://blog.csdn.net/willjet/article/details/5792689

http://bbs.21ic.com/icview-45502-1-1.html

作者:乱雪 来源:hi.baidu.com/lu4nx
R.E.C--F22叫我来篇稿,我实在不知道写啥,也很久没写过技术方面的东西了,刚看书时突然想到了写指针,所有的C语书上都把指针描述得很抽象,所以,老规矩,结合调试器+汇编来理解它。
其实指针和汇编中的间接寻址很像,抽象点说,运用指针可以间接性地访问某变量内容。我说得太抽象了,扔代码上来吧:

代码:

/*

C语指针演示,by:乱雪

2010.1.21

*/

#include <stdio.h>

int main(void)
{
int count = 10,lx, *pointer;  //定义两个整型变量count和lx,一个指针pointer。

pointer = &count;   //把count的内存地址赋给pointer,“&”是C语中的地址运算符,用于取内存地址
lx = *pointer;        //用“*”获得指针指向的内容,即 lx = 10
return 0;
}

这时lx的值就是10,可以加句printf("%d \n",lx);看到。
好了,进调试器来解释吧。编译环境VC6.0,调试器是VC6.0默认的调试器。需要观众有汇编语言的基础。
先在int count = 10,lx;处下个断点(右键——“Insert/Remove Breakpoint”),然后按F5,进入调试状态后,会自动在断点处停下来,此时点在断点行处右键——“Go To Disassembly”,来到汇编窗口。
这个时候断点处的代码如下:

代码:

mov         dword ptr [ebp-4],0Ah

0A是10的16进制,ebp-4是第一个变量count的地址,mov是传送指令。此句的意思是把10赋值给ebp-4,即count = 10。为了证明这一点,按一下F10单步运行,然后打开Watch窗口,在“名称”里键入&count,就看到了count的地址,如图:

 

在这里顺便说下,我们定义了三个变量,分别是count、lx和pointer,那么分别对应的地址是ebp-4、ebp-8、ebp-0Ch,因为整型变量占4字节内存,每次ebp都会减少4。
根据刚才代码中的顺序,下一句是pointer = &count;,将pointer指向count的内存地址,我们看对应的汇编代码:

代码:

lea         eax,[ebp-4]
mov         dword ptr [ebp-0Ch],eax

前面说过,ebp-4是count变量的内存地址,为了直观点,我把上面的汇编代码改一下:

代码:

lea         eax,[count]
mov         dword ptr [pointer],eax

lea指令就是把一个内存变量有效的地址传送给指定的寄存器。第一句lea   eax,[ebp-4]是把count的地址传到eax寄存器,根据刚才在Watch窗口中看到的count地址是0012ff7c,那么eax里面的值就是0012ff7c。第二句mov   dword ptr [ebp-0Ch],eax是把eax中的值传到ebp-0Ch(pointer)中。很明显,pointer = &count;这句代码就等同于pointer = 0012ff7c;。为了直观点,打开Registers窗口,观察寄存器。按三下F10执行三次单步运行,执行完mov dword ptr [ebp-0Ch],eax指令,这个时候寄存器内容如图:

 

看EAX的值正好是count变量的内存地址0012FF7C。然后在Watch窗口中输入pointer,可以看到pointer的内容是0x0012ff7c,如图:

 

说明pointer已经指到了count的内存地址了。
接下来看下一句代码lx = *pointer;,对应的汇编代码如下:

代码:

mov         ecx,dword ptr [ebp-0Ch]
mov         edx,dword ptr [ecx]
mov         dword ptr [ebp-8],edx

为了直观,我改一下代码:

代码:

mov         ecx,dword ptr [pointer]
mov         edx,dword ptr [ecx]
mov         dword ptr [lx],edx

这行代码意思是取出pointer指向的内容赋给变量lx。汇编代码中第一句mov   ecx,dword ptr [ebp-0Ch]意思是把ebp-0Ch(pointer)中的值传入到ecx寄存器中,刚才已经知道,pointer的内容是0x0012ff7c,那么这个时候ecx的值就是0x0012ff7c。第二句mov edx,dword ptr [ecx]意思是取出ecx寄存器中的地址对应的值放入edx寄存器,此时edx寄存器的值就是count的值10了。最后再mov dword ptr [ebp-8],edx,把edx寄存器中的值传到变量lx中。为了观察到整个过程,我们单步运行,直到运行完mov edx,dword ptr [ecx]这句指令,然后观察Registers窗口,看到ECX的值是0012FF7C。ECX的值已经是count的内存地址了:

 

再单步运行一次,运行完指令mov edx,dword ptr [ecx],再看Registers窗口,EDX的值是0000000A,A是10的十六进制表示。

 

简单总结下就是:首先将变量的地址放入寄存器中,然后再取出寄存器中存放地址对应的值。C指针的内幕就这样,自己跟着调试一次代码,就会理解了。如有不足之处欢迎一起讨论:)

c语言中指针和数组在编译时的区别 例如:int a[10]和int *b,a[5]和*(b+5) 在编译的时候区别是什么? 经过反复的查资料和请教他人,最后在《c专家编程》里找到了很好的答案。

    1。编译器对数组名和指针变量的处理方式 编译器在编译时会产生一个符号表,记录了符号名和它的地址。对于指针变量,这显然很好理解。而数组名就不那么明显了,它仅仅是一个符号而已,何来地址?编译器是这样处理的,它记录了array[0]的地址;这和我们通常的理解也是一样的。    

2。带下标形式的数组和指针寻址方式      

   (1)数组情形            

char a[9]="abcdefgh";             ...             c=a[i];

            在编译期,会在符号表中创建这样一条记录:             name:a  address:9980             要获取a[i]的值分两个步骤:            

step 1:取得i的值并和9980相加            

step 2:在内存地址(9980+i)处取其内容 
        (2)指针情形            

char* p="abcdefgh";             ...             c=p[i];            

在编译期,会在符号表中创建这样一条记录:             name:p  address:4624             要获取p[i]的值分三个步骤:            

step 1:在内存地址4624处取其内容,比如说“5081”            

step 2:取得i的值并和5081相加             step 3:在内存地址(5081+i)取其内容

来自:http://blog.csdn.net/hairetz/archive/2009/04/30/4141043.aspx

一、预备知识—程序的内存分配    一个由C/C++编译的程序占用的内存分为以下几个部分   

1、栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等。其    操作方式类似于数据结构中的栈。   

2、堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回    收   。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。   

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的    全局变量和静态变量在一块区域,   未初始化的全局变量和未初始化的静态变量在相邻的另    一块区域。   -   程序结束后由系统释放。   

4、文字常量区   —常量字符串就是放在这里的。   程序结束后由系统释放   

5、程序代码区—存放函数体的二进制代码。

二、例子程序     

这是一个前辈写的,非常详细     

//main.cpp     

int   a   =   0;   全局初始化区     

char   *p1;   全局未初始化区     

main()     

{     

int   b;   栈     

char   s[]   =   "abc";   栈     

char   *p2;   栈     

char   *p3   =   "123456";  123456/0在常量区,p3在栈上。     

static   int   c   =0;   全局(静态)初始化区     

p1   =   (char   *)malloc(10);     

p2   =   (char   *)malloc(20);      分配得来得10和20字节的区域就在堆区。     

strcpy(p1,   "123456");   123456/0放在常量区,编译器可能会将它与p3所指向的"123456"    优化成一个地方。   

  }   

2.6存取效率的比较        

char   s1[]   =   "aaaaaaaaaaaaaaa";     

char   *s2   =   "bbbbbbbbbbbbbbbbb";     

aaaaaaaaaaa是在运行时刻赋值的;     

而bbbbbbbbbbb是在编译时就确定的;     

但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。      比如:     

#include     

void   main()     

{     

char   a   =   1;     

char   c[]   =   "1234567890";     

char   *p   ="1234567890";     

a   =   c[1];     

a   =   p[1];     

return;     

}     

对应的汇编代码     

10:   a   =   c[1];      00401067   8A   4D   F1   mov   cl,byte   ptr   [ebp-0Fh]      0040106A   88   4D   FC   mov   byte   ptr   [ebp-4],cl     

11:   a   =   p[1];      0040106D   8B   55   EC   mov   edx,dword   ptr   [ebp-14h]      00401070   8A   42   01   mov   al,byte   ptr   [edx+1]      00401073   88   45   FC   mov   byte   ptr   [ebp-4],al     

第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到    edx中,再根据edx读取字符,显然慢了。   

来自:http://www.diybl.com/course/3_program/c++/cppsl/2008611/124612.html

是常量的情况:  把"abc"赋给一个字符指针变量时,如char* ptr = "abc";因为定义的是一个普通指针,并没有定义空间来存放"abc",所以编译器得帮找地方来放"abc",显然,把这里的"abc"当成常量并把它放到程序的常量区是编译器最合适的选择。所以尽管ptr的类型不是const char*,并且ptr[0] = 'x';也能编译通过,但是执行ptr[0] = 'x';就会发生运行时异常,因为这个语句试图去修改程序常量区中的东西。记得哪本书中曾经说过char* ptr = "abc";这种写法原来在c++标准中是不允许的,但是因为这种写法在c中实在是太多了,为了兼容c,不允许也得允许。虽然允许,但是建议的写法应该是const char* ptr = "abc";这样如果后面写ptr[0] = 'x'的话编译器就不会让它编译通过,也就避免了上面说的运行时异常。又扩展一下,如果char* ptr = "abc";写在函数体内,那么虽然这里的"abc/0"被放在常量区中,但是ptr本身只是一个普通的指针变量,所以ptr是被放在栈上的,只不过是它所指向的东西被放在常量区罢了。

来自:http://bbs.chinaunix.net/viewthread.php?tid=979141

回复 #1 specter117 的帖子

不知道你想要的是不是这个:

(1)对于顺序访问的而言,数组比链表效率高。
(2)对于数组,间接访问(指数组名+下标)绝不比指针访问的效率高。

比如:

int i; int b[100];

数组访问;  

for(i = 0; i < 100; i++)  

{    

b[i] = 0;

  }

指针访问:  

  int *p = b;   

for(i = 0; i < 100; i++)  

{        

*p++ = 0;  

}

数组访问时,b等价于*(b + i) =*( (char *)b + i * sizeof(int) ) 指针访问时,*p++等价于*(p + 1) = *((char *)p + 1 * sizeof(int))

由于sizeof是在编译时确定的,所以 b = *((char *) b + 4 * i) 而 *p++ = *((char *)p + 1) 间接访问有乘法运算,而指针访问只有加法运算,

所以在这里,间接访问没有指针访问效率高。

 

时间: 2024-11-16 14:19:25

C指针通过编译器实现汇编语言类似内存间接寻址功能,编程语言的差异主要来自编译器的解释的相关文章

字符串指针 内存非法-用指针对字符串排序的问题,内存访问非法(续)

问题描述 用指针对字符串排序的问题,内存访问非法(续) 还是上次类似的问题,求解答. 对字符串进行排序的问题,被指针搞糊涂了. #include<stdio.h> #include<string.h> #include<stdlib.h> int main() { void sort(char p[][5]); char ss[10][5]={"worin","trafi","panda","dala

printf-求大神解答,关于内存解析以及类型转换的编译器底层实现,图中的结果请给我一个完整的解释,谢谢

问题描述 求大神解答,关于内存解析以及类型转换的编译器底层实现,图中的结果请给我一个完整的解释,谢谢 #if 1 #include using namespace std; int main() { char str[] = "12345"; long pLong = (long)str; pLong++; printf("%dn", pLong); return 0; } #endif 解决方案 这代码在我的编译器上没法编译 我修改了下 #include <

c 指针 分配-C语言中通过指针能不能知道它在内存中的分配方案

问题描述 C语言中通过指针能不能知道它在内存中的分配方案 例如char* pstr; 有几种方式: pstr = "123"; char s[] = "123"; pstr = s; pstr = (char*)malloc(4); pstr[0] = '1'; pstr[1] = '2'; pstr[2] = '3'; pstr[3] = ''; 那么,如何通过pstr判断它是哪种方式的? 解决方案 这两种的结果都是一样的不必纠结,只有数组和指针在分配的时候是不同

Microsoft VBScript 编译器错误 错误 &#039;800a03e9&#039; 内存不够的解决方法_应用技巧

其实出现Microsoft VBScript 编译器错误 错误 '800a03e9' 内存不够的错误一般是由于访问量过大或攻击出现的,而iis进程回收还没有操作,所有我们手工回收一下就可以了. iis >应用程序池 > 找到对应网站的应用程序池 >右键 当然我们可以通过重启iis或重启服务器,在运行里面输入: iisreset /restart

Microsoft VBScript 编译器错误 错误 '800a03e9' 内存不够的解决方法

其实出现Microsoft VBScript 编译器错误 错误 '800a03e9' 内存不够的错误一般是由于访问量过大或攻击出现的,而iis进程回收还没有操作,所有我们手工回收一下就可以了. iis >应用程序池 > 找到对应网站的应用程序池 >右键 当然我们可以通过重启iis或重启服务器,在运行里面输入: iisreset /restart

c语言 程序 编译器 栈-C语言程序中,如何理解栈是由编译器管理的?

问题描述 C语言程序中,如何理解栈是由编译器管理的? 最近看书,有一句话"C语言程序中,栈是由编译器自动分配释放的".请问如何理解栈是由编译器管理的? 编译器不是在程序编译链接的时候用的么?而栈是在程序运行的时候产生的.那么如何理解栈是由编译器管理的? 解决方案 C语言对内存的管理的情况是:栈:'自动申请,自动释放',都这么说,栈的申请与释放都是编译器自己进行管理,当程序运行到是在栈上进行的动作的时候,就会自己进行对应的内存的分配与释放,例如,一个变量int a ,编译器会自己申请4字

win8系统自带内存诊断功能如何使用?

  win8系统使用自带内存诊断功能的方法: 1.按照上面的提示,选择推荐项默认的第一个,保存好现在需要保存的文档和工作后点击:立即重新启动并检查问题; 2.使用F1可以手动定义一些需要调整和修改以及测试类型的参数:这个是快速模式,Basic测试最少的内存项目,因此检测速度最快; 3.然后使用上下键进行选择可以看到标准界面,这个也是默认的执行模式; 4.最后一个检测模式是终极!也是检测项目最多,内容最苛刻的级别,如果想对您的内存进行一个彻底类烧鸡类型的检测可以选择此项; 默认情况下,诊断工具会对

Win8的内存诊断功能在哪里?

  微软从Vista开始就在开机菜单中添加了"内存诊断"组件,但是在Windows 8开机菜单中取消了这个组件,那么内存诊断功能在哪里呢,实际上内存诊断就是C:bootmemtest.exe程序,如果想在启动选择菜单项中使用的话可以进行如下操作: 首先我们需要下载VisualBcd 0.9.3.1,启动后单击菜单"Store→Open store",打开C:bootbcd文件,定位到Loaders,右击选择"New Vista/7/VHD",新建

js-aspnet实现类似winform的功能,点击按钮弹出窗口,原窗口重新绑定显示查询结果

问题描述 aspnet实现类似winform的功能,点击按钮弹出窗口,原窗口重新绑定显示查询结果 想通过aspnet实现一个功能,原页面有个按钮我通过showmodaldialog弹出一个窗口,窗口里面是一个表单,我希望表单的数据传递到原页面,原页面再根据这些参数,重新绑定gridview,得出查询结果,我的原页面gridview上面还有按钮,必须放在_!ispostback_里面,所以页面用session或者get方式获得参数的话,databind必须放在_ispostback_条件里面,因为