数组和指针背后——内存角度
上一篇《语义陷阱-数组和指针》
聊过数组和指针的区别,主要是对于数组和指针在内存中的访问方式加以区分,这篇博文则从更深层的角度剖析数组和指针的联系
如果你也对底层感兴趣、我向这篇文章会对你有所帮助,
什么时候数组和指针相同(When an Array Is a Pointer )
在实际应用中,他们可以互换的情形要大大多于不能互换的情形。首先再回顾一下声明和定义,(上一篇中有提到这里在深入一下)
声明本身还可以进一步分为三种情况:
1)外部数组的声明(external array)
2)数组的定义(它是声明的一种特殊情况,它分配内存空间,并可能提供一个初值)
3)函数参数的声明
所有作为函数参数的声明在编译时总是会转换为指针(指向数组第一个元素),而其他情况的声明,数组就是数组 ,指针就是指针
可以用如下的图来说明他们的关系:
稍微总结一下:
对于编译过程中数组会转化为指针的情况,数组和指针可以互换,比如:声明为函数参数的时候 fun(int a [])和fun(int *a )是等同的。因为编译的过程中fun(int a [])
中的数组会转化为指针形式,也就和fun(int *a )的效果一样了。
如果编译到时候数组不被当做指针处理,那么声明的时候不能等同。对于这种情况,数组和指针是不一样的的,在运行时的表示形式也不一样,并可能产生不同的代码。
对编译器而言,一个数组就是一个地址,一个指针就是地址的地址。
c语言标准对此做了如下说明:
1)An array name in an expression (in contrast with a declaration) is treated by the compiler as a pointer to the first element of the array
(paraphrase, ANSI C Standard, paragraph 6.2.2.1).
2)A subscript is always equivalent to an offset from a pointer (paraphrase,
ANSI C Standard, paragraph 6.3.2.1).
3)An array name in the declaration of a function parameter is treated by
the compiler as a pointer to the first element of the array (paraphrase, ANSI
C Standard, paragraph 6.7.1).
即:
1)表达式中的数组名(与声明不同)被编译器当做一个指向该数组第一个元素的指针
2)下标总是与偏移量相同
3)在函数参数的声明中,数组名被编译器当做指向该数组第一个元素的指针
我觉得有必要对上文中出现的“表达式”做一个解释
int arry[10]={,,,,,};
int a=arry[2];
那么第二句中的int a=arry[2];就是所谓的表达式中出现的数组名了,这个时候编译器会把数组名arry当做指向数组第一个元素的指针,也就是arry[0]的地址
下标总是与偏移量相同,也就是arry[2]中的下标2和arry[2]这个元素在内存中相对于第一个元素的偏移量也是2它们是相同的。这样就能解释用数组下标可以取得
相应的数组中的某个元素了。(当然,在内存中还要考虑步长因素)
有了上面的分析,下面的容易弄懂了
如果声明: int a[10] ,*p , i=2
那么我们可以通过下面的任一种方式来访问a[i](每一列为一组,共三种)
p=a; p=a; p=a+i;
p[i]; *(p+i); *p;
在表达式中,数组和指针是可以互换的, 因为它们在编译器里都是指针形式,并且都能进行去下标操作。
数组和指针的遍历
为了更好的理解,下面通过一个例子说明数组和指针的联系和区别。(有点难理解~哦)
将在内存访问的角度来讨论数组和指针遍历
数组遍历:
for(i=0;i<10;i++)
a[i]=0
遍历过程:
1)把a的左值放入寄存器R1(也就是把a的物理地址也即数组的首地址存入R1) 可提到循环外
2)把i 的左值放入R2,同上,就是吧i 的物理地址放入R2 可移到循环外
3)把 [R2] 的右值放入 R3 也就是把变量i 的大小放入R3,(这里有点汇编的味道)
4)如果需要,调整R3 的步长,把R1 +R3 的值放入R4 解释:R1为数组的首地址,R3为偏移量,所以R4 就是当前操作数的地址
5)把0放入 [R4]
注:上面的R1-R4 看看做是寄存器,符号 [n] 表示的是:内存地址为n的值
”可以移到循环外“说明它在整个过程中不会改变,比如数组的首地址,变量 i 地址
左值和右值的概念在上面链接给出的博文中有阐述
指针遍历:
p=a
for(i =0 ;i < 10 ; i ++)
*p++=0
遍历过程:
1)p 所指对象的大小放入R5 可移到循环外
2)左值 p 放入R1 可移到循环外
3)[R0]放入R1
4)0存到[ R1]
5) R5+R1的结果存入R1
6)R1 存到[R0]
其实,这两两种访问方式也可看做是对上一篇博客中的访问方式的一个更深的理解。
要操作一个变量就要得到这个变量的地址,取得地址的方式数组和指针的区别和联系。这里就不在啰嗦,有兴趣的朋友可以参见上一篇博文
http://www.cnblogs.com/yanlingyin/archive/2011/11/29/2268391.html
为什么C把数组形参当做指针
把作为形参的数组当做指针来考虑其实是出于效率考虑。C中,所有非数组形式的数据实参均为值传递形式,值传递也就是调用函数的时候,把实参
拷贝一份给调用函数,就是说函数操作的是实参的拷贝而不是实参本身。(所以值传递的时候如果在函数中改变参数值,等调用结束后对实际的实参没有
影响,因为值传递中函数操作的只是实参的一份拷贝而并不是实参本身)
而对于数组,如果每次执行函数都要拷贝整个数组的话,就会花费大量的时间和空间开销,所有对于数组,C开用的机制是告诉函数数组的首地址,直接对
数组进程操作。
了解C++的朋友对于这应该就能更好地理解了,C++中参数传递分为值传递和引用传递,有兴趣的可以自行查阅资料、这不是本文终点不在复数
数组和指针的可交换性总结:Arrays and Pointers Interchangeability Summary
1. An array access a[i] is always "rewritten" or interpreted by the compiler as a pointer access *(a+i);
2. Pointers are always just pointers; they are never rewritten to arrays. You can apply a
subscript to a pointer; you typically do this when the pointer is a function argument,
and you know that you will be passing an array in.
3. An array declaration in the specific context (only) of a function parameter can equally
be written as a pointer. An array that is a function argument (i.e., in a call to the
function) is always changed, by the compiler, to a pointer to the start of the array.
4. Therefore, you have the choice for defining a function parameter which is an array,
either as an array or as a pointer. Whichever way you define it, you actually get a
pointer inside the function.
5. In all other cases, definitions should match declarations. If you defined it as an array,
your extern declaration should be an array. And likewise for a pointer.
1)对于a [i]这种形式的访问数组,通常被解释为指针形式*(a + i) 也就是上文中所说的“表达式”的情形
2)指针就是指针,没有说指针转化为数组的情况,你可以用下标的形式去访问指针,但一般都是指针作为函数参数时,而且传入的是一个数组
3)在函数参数的声明中,数组可以看做指针,(也只有这种情况)
4)当把一个数组定义为函数参数时,可以定义为数组,也可以是指针
5)其他的所有情况,声明和定义必须匹配。如果定义了一个数组,在其他文件中对它也必须声明为数组。指针也一样。
参考资料:《expert c programming》
如转载请注明出处:http://www.cnblogs.com/yanlingyin/
一条鱼@博客园
2011-12-6