只有当数组名在表达式中使用的时候,编译器才会为它产生一个指针常量(所以不要把数组名和指针等同对待)。注意,是指针常量,而不是指针变量!你不能修改指针常量的值。
只有在两种场合下,数组名并不用指针常量来表示------就是当数组名作为sizeof操作符或单目操作符&到操作数时。sizeof 返回整个数组的长度,而不是指向数组名的指针的长度。
int array[10];
int *ap= array + 2;
在进行指针加法时会对2进行调整,调整方法:2*int 也就是将2的单位设置为array 的类型。
注意:C到下标引用和间接访问表达式是一样的!So ,ap[0] 等价的表达式是 *(ap+(0))把0和括号去掉,就为*ap,这个就好理解了,它的值为:array[2].
&ap 的值是无法预知到,因为你不知道编译器会把它放在什么地方;ap[-1]的值,ap现在指向array的第三个元素,所以使用偏移量-1,就可得到它到值array[1].
2[array]也是合法的,它的值是*(array+2).
效率:
讲效率之前,先说下linux下的反汇编:
objdump -j .text -Sl objtest | more
-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,
效果比较明显。隐含了-d参数。
-l 用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用
使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求
编译时使用了-g之类的调试编译选项。
-j name 仅仅显示指定section的信息
同时发现objdump这个指令功能强大,下面是几个我认为常用的option:
objdump -x obj 以某种分类信息的形式把目标文档的数据组织(被分为几大块)输出
objdump -t obj 输出目标文档的符号表
objdump -h obj 输出目标文档的section概括
下标绝对不会比指针更有效率,但指针有时会比下标更有效率。看以下代码及其反汇编(Centos系统)
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1,*p2;
try1()函数使用下标
void try1()
{
80483ee: 55 push %ebp
80483ef: 89 e5 mov %esp,%ebp //以上为函数调用的初始化,保存寄存器
/home/centoslive/Documents/c_test.c:30
for(i=0;i<SIZE;i++)
80483f1: c7 05 88 97 04 08 00 movl $0x0,0x8049788 //0x8049788 为变量i的地址,初始化为0
80483f8: 00 00 00
80483fb: eb 26 jmp 8048423 <try1+0x35> //跳转
/home/centoslive/Documents/c_test.c:31
x[i]=y[i]; // 以下10行为此函数的主循环
80483fd: a1 88 97 04 08 mov 0x8049788,%eax //将下标i存入寄存器
8048402: 8b 15 88 97 04 08 mov 0x8049788,%edx
8048408: 8b 14 95 a0 97 04 08 mov 0x80497a0(,%edx,4),%edx //0x80497a0为y数组的地址,相对偏移4*edx后,取内容存入edx
804840f: 89 14 85 c0 96 04 08 mov %edx,0x80496c0(,%eax,4) //0x80496c0为x数组的地址,将edx存入偏移量为4*eax的地址中
/home/centoslive/Documents/c_test.c:30
printf("%d\n",2[array]);
return 0;
}
void try1()
{
for(i=0;i<SIZE;i++)
8048416: a1 88 97 04 08 mov 0x8049788,%eax
804841b: 83 c0 01 add $0x1,%eax
804841e: a3 88 97 04 08 mov %eax,0x8049788
8048423: a1 88 97 04 08 mov 0x8049788,%eax //检测i是否大于50
8048428: 83 f8 31 cmp $0x31,%eax
804842b: 7e d0 jle 80483fd <try1+0xf> //小于等于则跳转
/home/centoslive/Documents/c_test.c:32
x[i]=y[i];
}
try2()函数使用指针,但效果并不是很好!
void try2()
{
804842f: 55 push %ebp
8048430: 89 e5 mov %esp,%ebp
/home/centoslive/Documents/c_test.c:35
for(p1=x,p2=y;p1-x<SIZE;*p1++=*p2++);
8048432: c7 05 c8 98 04 08 20 movl $0x8049720,0x80498c8 //0x8049720 x数组地址,0x80498c8 P1地址(p1指向x数组)
8048439: 97 04 08
804843c: c7 05 cc 98 04 08 00 movl $0x8049800,0x80498cc //0x8049800 y地址 0x80498cc p2地址
8048443: 98 04 08
8048446: eb 20 jmp 8048468 <try2+0x39> //使用指针后,主循环变成了16行!!
8048448: 8b 15 c8 98 04 08 mov 0x80498c8,%edx //p2 指向的内容放到p1中
804844e: a1 cc 98 04 08 mov 0x80498cc,%eax
8048453: 8b 08 mov (%eax),%ecx
8048455: 89 0a mov %ecx,(%edx)
8048457: 83 c2 04 add $0x4,%edx //调整指针
804845a: 89 15 c8 98 04 08 mov %edx,0x80498c8
8048460: 83 c0 04 add $0x4,%eax
8048463: a3 cc 98 04 08 mov %eax,0x80498cc
8048468: a1 c8 98 04 08 mov 0x80498c8,%eax //p1-x<SIZE ?
804846d: 89 c2 mov %eax,%edx
804846f: b8 20 97 04 08 mov $0x8049720,%eax
8048474: 89 d1 mov %edx,%ecx
8048476: 29 c1 sub %eax,%ecx
8048478: 89 c8 mov %ecx,%eax
804847a: 3d c7 00 00 00 cmp $0xc7,%eax
804847f: 7e c7 jle 8048448 <try2+0x19> //不大于则跳转
/home/centoslive/Documents/c_test.c:36
}
由以上代码可知,指针的使用并没有提高效率
try3()重新使用了计数器
void try3()
{
80483e9: 55 push %ebp
80483ea: 89 e5 mov %esp,%ebp
/home/centoslive/Documents/c_test.c:41
for(i=0,p1=x,p2=y;i<SIZE;i++)
80483ec: c7 05 a8 97 04 08 00 movl $0x0,0x80497a8 // i 赋初值
80483f3: 00 00 00
80483f6: c7 05 88 98 04 08 e0 movl $0x80496e0,0x8049888 // p1=x,p2=y
80483fd: 96 04 08
8048400: c7 05 8c 98 04 08 c0 movl $0x80497c0,0x804988c
8048407: 97 04 08
804840a: eb 2d jmp 8048439 <try3+0x50>
/home/centoslive/Documents/c_test.c:42
*p1++=*p2++;
804840c: 8b 15 88 98 04 08 mov 0x8049888,%edx // *p1++=*p2++;
8048412: a1 8c 98 04 08 mov 0x804988c,%eax
8048417: 8b 08 mov (%eax),%ecx
8048419: 89 0a mov %ecx,(%edx)
804841b: 83 c2 04 add $0x4,%edx //调整指针
804841e: 89 15 88 98 04 08 mov %edx,0x8049888
8048424: 83 c0 04 add $0x4,%eax
8048427: a3 8c 98 04 08 mov %eax,0x804988c
/home/centoslive/Documents/c_test.c:41
for(p1=x,p2=y;p1-x<SIZE;*p1++=*p2++);
}
void try3()
{
for(i=0,p1=x,p2=y;i<SIZE;i++)
804842c: a1 a8 97 04 08 mov 0x80497a8,%eax
8048431: 83 c0 01 add $0x1,%eax // i ++
8048434: a3 a8 97 04 08 mov %eax,0x80497a8
8048439: a1 a8 97 04 08 mov 0x80497a8,%eax // i <50?
804843e: 83 f8 31 cmp $0x31,%eax
8048441: 7e c9 jle 804840c <try3+0x23>
/home/centoslive/Documents/c_test.c:43
*p1++=*p2++;
}
try4()使用寄存器指针变量
void try4()
{
80483e9: 55 push %ebp //保护需要使用的寄存器
80483ea: 89 e5 mov %esp,%ebp
80483ec: 57 push %edi
80483ed: 56 push %esi
80483ee: 53 push %ebx
/home/centoslive/Documents/c_test.c:50
register int *pt1,*pt2;
register int i;
for(i=0,pt1=x,pt2=y;i<SIZE;i++)
80483ef: bf 00 00 00 00 mov $0x0,%edi // edi 寄存器用来存放变量 i 的值
80483f4: be c0 96 04 08 mov $0x80496c0,%esi // esi寄存器用来存放 指针变量pt1
80483f9: bb a0 97 04 08 mov $0x80497a0,%ebx // ebx 寄存器用来存放指针变量pt2
80483fe: eb 0d jmp 804840d <try4+0x24>
/home/centoslive/Documents/c_test.c:51
*pt1++=*pt2++;
8048400: 8b 03 mov (%ebx),%eax
8048402: 89 06 mov %eax,(%esi)
8048404: 83 c6 04 add $0x4,%esi
8048407: 83 c3 04 add $0x4,%ebx
/home/centoslive/Documents/c_test.c:50
*/
void try4()
{
register int *pt1,*pt2;
register int i;
for(i=0,pt1=x,pt2=y;i<SIZE;i++)
804840a: 83 c7 01 add $0x1,%edi
804840d: 83 ff 31 cmp $0x31,%edi
8048410: 7e ee jle 8048400 <try4+0x17>
/home/centoslive/Documents/c_test.c:52
*pt1++=*pt2++;
}
try5() 在使用寄存器指针变量到基础上消除了计数器
void try5()
{
80483e9: 55 push %ebp
80483ea: 89 e5 mov %esp,%ebp
80483ec: 56 push %esi
80483ed: 53 push %ebx
/home/centoslive/Documents/c_test.c:56
register int *pt1,*pt2;
for(pt1=x,pt2=y;pt1<&x[SIZE];)
80483ee: bb c0 96 04 08 mov $0x80496c0,%ebx
80483f3: be a0 97 04 08 mov $0x80497a0,%esi
80483f8: eb 0a jmp 8048404 <try5+0x1b>
/home/centoslive/Documents/c_test.c:57
*pt1++=*pt2++; //此时主要代码达到最紧凑。。。7行
80483fa: 8b 06 mov (%esi),%eax
80483fc: 89 03 mov %eax,(%ebx)
80483fe: 83 c3 04 add $0x4,%ebx
8048401: 83 c6 04 add $0x4,%esi
/home/centoslive/Documents/c_test.c:56
}
*/
void try5()
{
register int *pt1,*pt2;
for(pt1=x,pt2=y;pt1<&x[SIZE];)
8048404: b8 88 97 04 08 mov $0x8049788,%eax
8048409: 39 c3 cmp %eax,%ebx
804840b: 72 ed jb 80483fa <try5+0x11>
/home/centoslive/Documents/c_test.c:58
*pt1++=*pt2++;
}
通过以上的实验可知:
1、当你根据某个固定数目的增量在一个数组中移动的时候,使用指针变量将比使用下标产生效率更高的代码。当这个增量是1并且机器具有地址自动增量模型时,这表现的更为突出。
2、声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高(提高幅度取决于你使用的机器)
3、如果你可以通过测试一些已经初始化并且经过调整的内容来判断循环是否应该终止,那么你就不需要使用一个单独到计数器
4、那些必须在运行时求值的表达式较之诸如&array[SIZE],或者array+SIZE 这样到变量表达式往往代价更高!