发布日期:2015-12-22 14:27 来源: 标签: 编程语言 C++教程 C++指针内存访问 C++数组
本章我们主要学习C++数组与指针在内存访问方式上的区别?下面我们就做一下具体讲解,希望大家多多支持中国站长网络学院。
首先看一个例子:以下两个程序,程序1使用整形变量,程序2使用整形指针,但是在编译运行后,会是什么样的?
program 1:
    #include <iostream>
     int main(){
        int int_input;
        cin>>int_input;
        cout<<(int_input + 4)<<endl;
        return 0;
    }
program 2:
   #include <iostream>
    int main(){
        int *int_ptr = new int[1];
        cin>>*int_ptr;
        cout<< (*int_ptr + 4)<<endl;
        delete(int_ptr);
        return 0;
    }
答案显而易见,都是需要你输入一个整数,然后输出刚才输入的值。问题就来了,这两个程序运行结果是一样的,你能说这两个程序是完全一样的么?
通过以上简单的引入例子,可以对数组和指针有个简单认识,接下来本文从数组和指针在访问方式上的区别、在函数调用上的区别以及数组和指针内在的联系上进行分析。
一、在内存访问方式上,数组和指针的区别
为了对数组和指针对内存访问方式的理解,首先对“地址y”和“地址y的内容”做简单的区别,详细内容如下图:

编译器为每个变量分配地址(左值)。这个地址在编译时可知且一直存在,而它的右值在运行时才能知道。通俗的说:每个变量都有一个地址,这个地址在编译时可以知道,而地址里存储的内容(也就是变量的右值)只有在运行时才能知道。如果需要用到变量的值(也就是已知地址存储的值),那么编译器发出指令从指定地址读入变量值并放入相应寄存器中。
(1)数组的访问方式
   关键之处在于每个符号的地址在编译时可知,如果编译器需要一个地址(比如说加上偏移量)来执行某种操作,可以可以直接操作。相反、对于指针,必须在运行时取得它的地址,然后才能对它进行接触引用操作。下图A展示了对数组下标的引用,体现数组的访问形式:

看到这里,就明白为什么extern char a[]和extern char a[100]相同的原因了。这两个声明都是提示a是一个数组,也就是一个内存地址,数组内的字符可以由这个地址(加偏移量)找到。
(2)指针的访问方式
  若声明的是一个指针,如  extern char *p,它表示p指向一个字符,为了取得这个字符,必须知道地址p的内容,把它作为字符的地址并从这个地址中取得这个字符。如下图B所示,体现指针访问的形式。

(3)若“定义为指针,但是以数组的方式引用”,这样会发生什么?
    当一个外部数组的实际定义是一个指针,但却以数组的方式对其引用时,会发生些什么?会向图A中方式直接的引用访问?事实是编译器所执行的是图B中的对内存间接引用。因为我们告诉编译器拥有的一个指针,如图C所示。

我们这里对照图C中的访问方式:
char *p = "abcdefgh";  ...  p[3]; 
与图A中的访问方式:
char a[] = "abcdefgh";   ...  a[3];
这两种方式都可以取得字符‘d’,但是途径不一致。
(4)数组和指针的一些特点对比
表1 数组和指针的区别

数组和指针可以在定义中用字符串常量进行初始化,尽管看上去一样,底层的机制却不同。
定义指针时,编译器并不为它所指向的对象分配空间,只为指针本身分配空间。除非在定义同时付给一个指针一字符窜常量进行初始化。
如:char *p = "breadfruit";   (注意:只有对字符串常量才是如此,不能指望为浮点数之类的常量分配空间例如:float *p = 3.1415 ,!!!这是错误的)
一般情况下初始化指针时创建的字符串变量被定义为只读。如果试图修改就会出现未定义的行为。
数组可以用字符串常量进行初始化:
    char a[] = “gooseberry“;
与指针相反,由字符串常量初始化的数组可以修改的。
二、再论数组和指针
   在内存访问方式上,数组和指针存在区别。意识到数组和指针是不同的,但是在实际应用中有时候数组和指针却是相同的!?多么神奇!如果你是编程老手,就会知道在实际应用中数组和指针可以互换的情形要比两者不可互换的情形更为常见。
(1)“声明”和“使用”情形下
声明本身还可以进一步分为三种情况:
1)外部数组的声明(external array)
2)数组的定义(它是声明的一种特殊情况,它分配内存空间,并可能提供一个初值)
3)函数参数的声明
所有作为函数参数的数组名在编译时是会转换为指针(有种说法是“退化为指针”,一个意思),而其他情况的声明,数组的声明就是数组 ,指针的声明就是指针,两者不混淆。但是在使用数组时,数组总是可以写成指针的形式,两者可以互换。如下图所示:

上图在声明和使用上说明了数组和指针在什么情况下可以互换使用,即是相同的。然而,数组和指针在编译器处理时是不同的,在运行时的表现形式也是不同的,并可能产生不同的代码,这点是需要牢记的!具体原因,如果看了前面的第一部分内容,很容易理解。
(2)什么时候数组和指针是相同的
在C语言标准中,对此有如下说明
1)表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针;
在表达式中,指针和数组是可以互换的,因为他们在编译器中最终形式都是指针。如何理解这句话,请看下面的这个例子。
例子:
     int a[10],*p,i = 2;
可以通过以下任一种方式访问a[i]

原因是对数组的引用如a[i]在编译时总是被编译器改写成*(a+i)的形式。
2)下标总是与指针的偏移量相同;
C语言中,把数组下标作为指针的偏移量。根本原因是指针和偏移量是底层硬件所使用的基本模型。
3)在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针;
理解这句话首先要明白形参和实参的区别,

在函数参数这种特殊情况下,编译器必须把数组名当作指向该数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。
(3)数组和指针可交换型总结
    a)对于a [i]这种形式的访问数组,通常被解释为指针形式*(a + i)   也就是上文中所说的“表达式”的情形
    b)指针就是指针,没有说指针转化为数组的情况,你可以用下标的形式去访问指针,但一般都是指针作为函数参数时,而且传入的是一个数组
    c)在函数参数的声明中,数组可以看做指针,(也只有这种情况)
    d)当把一个数组定义为函数参数时,可以定义为数组,也可以是指针
    e)其他的所有情况,声明和定义必须匹配。如果定义了一个数组,在其他文件中对它也必须声明为数组。指针也一样。
(4)数组和指针参数是如何被编译器修改的
上部分中提到编译器对数组到指针的修改,所以这里补充一点东西。“数组名被改写成一个指针参数”规则不是递归定义的。就是说数组的数组会被改写成“数组的指针”,而不是“指针的指针”。

三、对引文中问题的解答
这里我们在内存中方式的不同来看这个问题。查看其汇编代码答案就一目了然了。
第一个程序:整型变量的
#include <iostream>
int main(){
int int_input;
cin>>int_input;
cout<<(int_input + 4)<<endl;
return 0;
}
汇编程序:
2212: main(){
00401000   push        ebp
00401001   mov         ebp,esp
00401003   sub         esp,44h
00401006   push        ebx
00401007   push        esi
00401008   push        edi
2213:      int int_input;
2214:      cin>>int_input;
00401009   lea         eax,[ebp-4]
0040100C   push        eax
0040100D   mov         ecx,offset cin (00414c58)
00401012   call        istream::operator>> (0040b7c0)
2215:      cout<<(int_input+4)<<endl;
00401017   push        offset endl (00401070)
0040101C   mov         ecx,dword ptr [ebp-4]
0040101F   add         ecx,4
00401022   push        ecx
00401023   mov         ecx,offset cout (00414c18)
00401028   call        ostream::operator<< (0040b3e0)
0040102D   mov         ecx,eax
0040102F   call        ostream::operator<< (00401040)
2216:      return 0;
00401034   xor         eax,eax
2217: }
第二个程序:整形指针的
#include <iostream>
int main(){
int *int_ptr = new int[1];
cin>>*int_ptr;
cout<< (*int_ptr + 4)<<endl;
delete(int_ptr);
return 0;
}
汇编程序:
2212: main(){
00401000   push        ebp
00401001   mov         ebp,esp
00401003   sub         esp,4Ch
00401006   push        ebx
00401007   push        esi
00401008   push        edi
2213:      int *int_ptr = new int[1];
00401009   push        4
0040100B   call        operator new (004011b0)
00401010   add         esp,4
00401013   mov         dword ptr [ebp-8],eax
00401016   mov         eax,dword ptr [ebp-8]
00401019   mov         dword ptr [ebp-4],eax
2214:      cin>>*int_ptr;
0040101C   mov         ecx,dword ptr [ebp-4]
0040101F   push        ecx
00401020   mov         ecx,offset cin (00414c38)
00401025   call        istream::operator>> (0040b8a0)
2215:      cout<< (*int_ptr + 4)<<endl;
0040102A   push        offset endl (004010a0)
0040102F   mov         edx,dword ptr [ebp-4]
00401032   mov         eax,dword ptr [edx]
00401034   add         eax,4
00401037   push        eax
00401038   mov         ecx,offset cout (00414bf8)
0040103D   call        ostream::operator<< (0040b4c0)
00401042   mov         ecx,eax
00401044   call        ostream::operator<< (00401070)
2216:      delete(int_ptr);
00401049   mov         ecx,dword ptr [ebp-4]
0040104C   mov         dword ptr [ebp-0Ch],ecx
0040104F   mov         edx,dword ptr [ebp-0Ch]
00401052   push        edx
00401053   call        operator delete (00401120)
00401058   add         esp,4
2217:      return 0;
0040105B   xor         eax,eax
2218: }
通过汇编代码来看,在变量定、输入以及输出时看出整型变量不同于指向整形的指针。

相关评论

专题信息
    C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。C++支持多种编程范式 --面向对象编程、泛型编程和过程化编程。最新正式标准C++于2014年8月18日公布。 其编程领域众广,常用于系统开发,引擎开发等应用领域,是至今为止最受广大程序员受用的最强大编程语言之一,支持类:类、封装、重载等特性! 本教程从基础讲解了C++语言,希望对大家有所帮助,望多多支持中国站长网络学院。