在上一篇博文 代码优化小技巧(持续更新......) 第三条关于数组和指针谁更高效, 意犹未尽, 决定单独拉出一篇来讲
1. 数组和指针操作对比
#include <stdio.h> int main()
{
char *char_p, char_arr[]={,,};
short *short_p, short_arr[]={,,};
int *int_p, int_arr[]={,,}; char_p=char_arr;
short_p=short_arr;
int_p=int_arr; printf("111\n");
(*(char_p+)) ++;
printf("222\n");
char_arr[] ++; printf("111\n");
(*(short_p+)) ++;
printf("222\n");
short_arr[] ++; printf("111\n");
(*(int_p+)) ++;
printf("222\n");
int_arr[] ++; return ;
}
编译和反汇编
<main>:
: push %rbp
: e5 mov %rsp,%rbp
40059a: ec sub $0x60,%rsp
40059e: 8b mov %fs:0x28,%rax
4005a5:
4005a7: f8 mov %rax,-0x8(%rbp)
4005ab: c0 xor %eax,%eax
4005ad: c7 f0 movl $0x0,-0x10(%rbp)
4005b4: c6 f4 movb $0x0,-0xc(%rbp)
4005b8: c6 f1 movb $0x1,-0xf(%rbp)
4005bc: c6 f2 movb $0x2,-0xe(%rbp)
4005c0: c7 c0 movq $0x0,-0x40(%rbp)
4005c7:
4005c8: c7 c8 movw $0x0,-0x38(%rbp)
4005ce: c7 c0 movw $0x1,-0x40(%rbp)
4005d4: c7 c2 movw $0x2,-0x3e(%rbp)
4005da: c7 c4 movw $0x3,-0x3c(%rbp)
4005e0: c7 d0 movq $0x0,-0x30(%rbp)
4005e7:
4005e8: c7 d8 movq $0x0,-0x28(%rbp)
4005ef:
4005f0: c7 e0 movl $0x0,-0x20(%rbp)
4005f7: c7 d0 movl $0x2,-0x30(%rbp)
4005fe: c7 d4 movl $0x3,-0x2c(%rbp)
: c7 d8 movl $0x4,-0x28(%rbp)
40060c: 8d f0 lea -0x10(%rbp),%rax
: a8 mov %rax,-0x58(%rbp)
: 8d c0 lea -0x40(%rbp),%rax
: b0 mov %rax,-0x50(%rbp)
40061c: 8d d0 lea -0x30(%rbp),%rax
: b8 mov %rax,-0x48(%rbp)
: bf mov $0x400754,%edi
: e8 fe ff ff callq <puts@plt>
40062e: 8b a8 mov -0x58(%rbp),%rax
: c0 add $0x2,%rax
: 0f b6 movzbl (%rax),%edx
: c2 add $0x1,%edx
40063c: mov %dl,(%rax)
40063e: bf mov $0x400758,%edi
: e8 fe ff ff callq <puts@plt>
: 0f b6 f2 movzbl -0xe(%rbp),%eax
40064c: c0 add $0x1,%eax
40064f: f2 mov %al,-0xe(%rbp)
: bf mov $0x400754,%edi
: e8 fe ff ff callq <puts@plt>
40065c: 8b b0 mov -0x50(%rbp),%rax
: c0 add $0x4,%rax
: 0f b7 movzwl (%rax),%edx
: c2 add $0x1,%edx
40066a: mov %dx,(%rax)
40066d: bf mov $0x400758,%edi
: e8 e9 fd ff ff callq <puts@plt>
: 0f b7 c4 movzwl -0x3c(%rbp),%eax
40067b: c0 add $0x1,%eax
40067e: c4 mov %ax,-0x3c(%rbp)
: bf mov $0x400754,%edi
: e8 d4 fd ff ff callq <puts@plt>
40068c: 8b b8 mov -0x48(%rbp),%rax
: c0 add $0x8,%rax
: 8b mov (%rax),%edx
: c2 add $0x1,%edx
: mov %edx,(%rax)
40069b: bf mov $0x400758,%edi
4006a0: e8 bb fd ff ff callq <puts@plt>
4006a5: 8b d8 mov -0x28(%rbp),%eax
4006a8: c0 add $0x1,%eax
4006ab: d8 mov %eax,-0x28(%rbp)
4006ae: b8 mov $0x0,%eax
4006b3: 8b 4d f8 mov -0x8(%rbp),%rcx
4006b7: 0c xor %fs:0x28,%rcx
4006be:
4006c0: je 4006c7 <main+0x131>
4006c2: e8 a9 fd ff ff callq <__stack_chk_fail@plt>
4006c7: c9 leaveq
4006c8: c3 retq
4006c9: 0f 1f nopl 0x0(%rax)
x86编译和反汇编
0000842c <main>:
842c: e92d4800 push {fp, lr}
: e28db004 add fp, sp, #
: e24dd038 sub sp, sp, # ; 0x38
: e3a03000 mov r3, #
843c: e50b3018 str r3, [fp, #-] ; 0xffffffe8
: e3a03000 mov r3, #
: e54b3014 strb r3, [fp, #-] ; 0xffffffec
: e3a03001 mov r3, #
844c: e54b3017 strb r3, [fp, #-] ; 0xffffffe9
: e3a03002 mov r3, #
: e54b3016 strb r3, [fp, #-] ; 0xffffffea
: e24b3024 sub r3, fp, # ; 0x24
845c: e3a02000 mov r2, #
: e5832000 str r2, [r3]
: e2833004 add r3, r3, #
: e3a02000 mov r2, #
846c: e5832000 str r2, [r3]
: e2833004 add r3, r3, #
: e3a02000 mov r2, #
: e1c320b0 strh r2, [r3]
847c: e2833002 add r3, r3, #
: e3a03001 mov r3, #
: e14b32b4 strh r3, [fp, #-] ; 0xffffffdc
: e3a03002 mov r3, #
848c: e14b32b2 strh r3, [fp, #-] ; 0xffffffde
: e3a03003 mov r3, #
: e14b32b0 strh r3, [fp, #-] ; 0xffffffe0
: e24b3038 sub r3, fp, # ; 0x38
849c: e3a02000 mov r2, #
84a0: e5832000 str r2, [r3]
84a4: e2833004 add r3, r3, #
84a8: e3a02000 mov r2, #
84ac: e5832000 str r2, [r3]
84b0: e2833004 add r3, r3, #
84b4: e3a02000 mov r2, #
84b8: e5832000 str r2, [r3]
84bc: e2833004 add r3, r3, #
84c0: e3a02000 mov r2, #
84c4: e5832000 str r2, [r3]
84c8: e2833004 add r3, r3, #
84cc: e3a02000 mov r2, #
84d0: e5832000 str r2, [r3]
84d4: e2833004 add r3, r3, #
84d8: e3a03002 mov r3, #
84dc: e50b3038 str r3, [fp, #-] ; 0xffffffc8
84e0: e3a03003 mov r3, #
84e4: e50b3034 str r3, [fp, #-] ; 0xffffffcc
84e8: e3a03004 mov r3, #
84ec: e50b3030 str r3, [fp, #-] ; 0xffffffd0
84f0: e24b3018 sub r3, fp, #
84f4: e50b3008 str r3, [fp, #-]
84f8: e24b3024 sub r3, fp, # ; 0x24
84fc: e50b300c str r3, [fp, #-]
: e24b3038 sub r3, fp, # ; 0x38
: e50b3010 str r3, [fp, #-]
: e59f00b0 ldr r0, [pc, #] ; 85c0 <main+0x194>
850c: ebffff8f bl <_init+0x20>
: e51b3008 ldr r3, [fp, #-]
: e2833002 add r3, r3, #
: e5d32000 ldrb r2, [r3]
851c: e2822001 add r2, r2, #
: e20220ff and r2, r2, # ; 0xff
: e5c32000 strb r2, [r3]
: e59f0094 ldr r0, [pc, #] ; 85c4 <main+0x198>
852c: ebffff87 bl <_init+0x20>
: e55b3016 ldrb r3, [fp, #-] ; 0xffffffea
: e2833001 add r3, r3, #
: e20330ff and r3, r3, # ; 0xff
853c: e54b3016 strb r3, [fp, #-] ; 0xffffffea
: e59f0078 ldr r0, [pc, #] ; 85c0 <main+0x194>
: ebffff81 bl <_init+0x20>
: e51b300c ldr r3, [fp, #-]
854c: e2833004 add r3, r3, #
: e1d320b0 ldrh r2, [r3]
: e2822001 add r2, r2, #
: e1a02802 lsl r2, r2, #
855c: e1a02822 lsr r2, r2, #
: e1c320b0 strh r2, [r3]
: e59f0058 ldr r0, [pc, #] ; 85c4 <main+0x198>
: ebffff78 bl <_init+0x20>
856c: e15b32b0 ldrh r3, [fp, #-] ; 0xffffffe0
: e2833001 add r3, r3, #
: e1a03803 lsl r3, r3, #
: e1a03823 lsr r3, r3, #
857c: e14b32b0 strh r3, [fp, #-] ; 0xffffffe0
: e59f0038 ldr r0, [pc, #] ; 85c0 <main+0x194>
: ebffff71 bl <_init+0x20>
: e51b3010 ldr r3, [fp, #-]
858c: e2833008 add r3, r3, #
: e5932000 ldr r2, [r3]
: e2822001 add r2, r2, #
: e5832000 str r2, [r3]
859c: e59f0020 ldr r0, [pc, #] ; 85c4 <main+0x198>
85a0: ebffff6a bl <_init+0x20>
85a4: e51b3030 ldr r3, [fp, #-] ; 0xffffffd0
85a8: e2833001 add r3, r3, #
85ac: e50b3030 str r3, [fp, #-] ; 0xffffffd0
85b0: e3a03000 mov r3, #
85b4: e1a00003 mov r0, r3
85b8: e24bd004 sub sp, fp, #
85bc: e8bd8800 pop {fp, pc}
85c0: 000086a0 .word 0x000086a0
85c4: 000086a4 .word 0x000086a4
arm编译和反汇编
横向对比:
这里可以很明显得出结论: 使用数组操作比指针高效!, 理由很简单, 编译器认为数组偏移多少成员其对于地址都是确定的, 取数组[0]和[3]没有区别就是个地址, 而指针偏移是一个独立行为,
所以要显性执行这个动作, 因此多出这部分指令!
这个表还有其他有意思的地方, 比如用int变量比char、short高效, char要and或者movzbl屏蔽溢出, short要lsl/lsr左移右移等
另外就是x86可以直接通过mov操作内存, 而ARM结构采用load-store, 必须先加载到寄存器, 进行操作后再回写内存
2. 指针作为函数参数(x86编译为例)
#include <stdio.h> void test1(int *p)
{
(*(p+))++;
} void test2(int *p)
{
p[]++;
} int main()
{
char *char_p, char_arr[]={,,};
short *short_p, short_arr[]={,,};
int *int_p, int_arr[]={,,}; char_p=char_arr;
short_p=short_arr;
int_p=int_arr; /* 省略上面测试代码*/ printf("333\n");
test1(int_p);
test2(int_p);
printf("444\n");
test1(int_arr);
test2(int_arr); return ;
}
可以发现test1()、test2()反汇编实现是一样的, 不会因为“形式”上我用数组还是指针, 最终的本质是指针, 而调用无论传的是数组地址还是指针地址, 没有影响, 都是地址值而已
<test1>:
: push %rbp
: e5 mov %rsp,%rbp
40059a: 7d f8 mov %rdi,-0x8(%rbp)
40059e: 8b f8 mov -0x8(%rbp),%rax
4005a2: c0 add $0x10,%rax
4005a6: 8b mov (%rax),%edx
4005a8: c2 add $0x1,%edx
4005ab: mov %edx,(%rax)
4005ad: nop
4005ae: 5d pop %rbp
4005af: c3 retq 00000000004005b0 <test2>:
4005b0: push %rbp
4005b1: e5 mov %rsp,%rbp
4005b4: 7d f8 mov %rdi,-0x8(%rbp)
4005b8: 8b f8 mov -0x8(%rbp),%rax
4005bc: c0 add $0x10,%rax
4005c0: 8b mov (%rax),%edx
4005c2: c2 add $0x1,%edx
4005c5: mov %edx,(%rax)
4005c7: nop
4005c8: 5d pop %rbp
4005c9: c3 retq 4006e7: e8 fd ff ff callq <puts@plt>
4006ec: 8b b8 mov -0x48(%rbp),%rax
4006f0: c7 mov %rax,%rdi
4006f3: e8 9e fe ff ff callq <test1>
4006f8: 8b b8 mov -0x48(%rbp),%rax
4006fc: c7 mov %rax,%rdi
4006ff: e8 ac fe ff ff callq 4005b0 <test2>
: bf e0 mov $0x4007e0,%edi
: e8 fd ff ff callq <puts@plt>
40070e: 8d d0 lea -0x30(%rbp),%rax
: c7 mov %rax,%rdi
: e8 7c fe ff ff callq <test1>
40071a: 8d d0 lea -0x30(%rbp),%rax
40071e: c7 mov %rax,%rdi
: e8 8a fe ff ff callq 4005b0 <test2>
3. 数组作为函数参数(x86编译为例)
代码和上面一样就不贴了, 只是将参数改成数组
/*
void test1(int *p)
{
(*(p+4))++;
} void test2(int *p)
{
p[4]++;
}
*/
void test1(int p[])
{
(*(p+))++;
} void test2(int p[])
{
p[]++;
}
反汇编的结果发现和上面函数形参是指针的一模一样! 也就是说虽然我的参数看起来像数组, 但实际上由于没有指定成员数量实际分配内存, 所以编译器还是把参数当做指针对待!
结论:
a. “真实”数组操作比指针高效
b. 有些看起来像数组, 实质是指针的, 指令走的还是指针那一套, 无论语法写的是*p++还是p[i]