在上一篇博文 代码优化小技巧(持续更新......) 第三条关于数组和指针谁更高效, 意犹未尽, 决定单独拉出一篇来讲
1. 数组和指针操作对比
#include <stdio.h> int main() { char *char_p, char_arr[5]={0,1,2}; short *short_p, short_arr[5]={1,2,3}; int *int_p, int_arr[5]={2,3,4}; char_p=char_arr; short_p=short_arr; int_p=int_arr; printf(\"111\\n\"); (*(char_p+2)) ++; printf(\"222\\n\"); char_arr[2] ++; printf(\"111\\n\"); (*(short_p+2)) ++; printf(\"222\\n\"); short_arr[2] ++; printf(\"111\\n\"); (*(int_p+2)) ++; printf(\"222\\n\"); int_arr[2] ++; return 0; }
编译和反汇编
<main>: 400596: 55 push %rbp 400597: 48 89 e5 mov %rsp,%rbp 40059a: 48 83 ec 60 sub $0x60,%rsp 40059e: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4005a5: 00 00 4005a7: 48 89 45 f8 mov %rax,-0x8(%rbp) 4005ab: 31 c0 xor %eax,%eax 4005ad: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%rbp) 4005b4: c6 45 f4 00 movb $0x0,-0xc(%rbp) 4005b8: c6 45 f1 01 movb $0x1,-0xf(%rbp) 4005bc: c6 45 f2 02 movb $0x2,-0xe(%rbp) 4005c0: 48 c7 45 c0 00 00 00 movq $0x0,-0x40(%rbp) 4005c7: 00 4005c8: 66 c7 45 c8 00 00 movw $0x0,-0x38(%rbp) 4005ce: 66 c7 45 c0 01 00 movw $0x1,-0x40(%rbp) 4005d4: 66 c7 45 c2 02 00 movw $0x2,-0x3e(%rbp) 4005da: 66 c7 45 c4 03 00 movw $0x3,-0x3c(%rbp) 4005e0: 48 c7 45 d0 00 00 00 movq $0x0,-0x30(%rbp) 4005e7: 00 4005e8: 48 c7 45 d8 00 00 00 movq $0x0,-0x28(%rbp) 4005ef: 00 4005f0: c7 45 e0 00 00 00 00 movl $0x0,-0x20(%rbp) 4005f7: c7 45 d0 02 00 00 00 movl $0x2,-0x30(%rbp) 4005fe: c7 45 d4 03 00 00 00 movl $0x3,-0x2c(%rbp) 400605: c7 45 d8 04 00 00 00 movl $0x4,-0x28(%rbp) 40060c: 48 8d 45 f0 lea -0x10(%rbp),%rax 400610: 48 89 45 a8 mov %rax,-0x58(%rbp) 400614: 48 8d 45 c0 lea -0x40(%rbp),%rax 400618: 48 89 45 b0 mov %rax,-0x50(%rbp) 40061c: 48 8d 45 d0 lea -0x30(%rbp),%rax 400620: 48 89 45 b8 mov %rax,-0x48(%rbp) 400624: bf 54 07 40 00 mov $0x400754,%edi 400629: e8 32 fe ff ff callq 400460 <puts@plt> 40062e: 48 8b 45 a8 mov -0x58(%rbp),%rax 400632: 48 83 c0 02 add $0x2,%rax 400636: 0f b6 10 movzbl (%rax),%edx 400639: 83 c2 01 add $0x1,%edx 40063c: 88 10 mov %dl,(%rax) 40063e: bf 58 07 40 00 mov $0x400758,%edi 400643: e8 18 fe ff ff callq 400460 <puts@plt> 400648: 0f b6 45 f2 movzbl -0xe(%rbp),%eax 40064c: 83 c0 01 add $0x1,%eax 40064f: 88 45 f2 mov %al,-0xe(%rbp) 400652: bf 54 07 40 00 mov $0x400754,%edi 400657: e8 04 fe ff ff callq 400460 <puts@plt> 40065c: 48 8b 45 b0 mov -0x50(%rbp),%rax 400660: 48 83 c0 04 add $0x4,%rax 400664: 0f b7 10 movzwl (%rax),%edx 400667: 83 c2 01 add $0x1,%edx 40066a: 66 89 10 mov %dx,(%rax) 40066d: bf 58 07 40 00 mov $0x400758,%edi 400672: e8 e9 fd ff ff callq 400460 <puts@plt> 400677: 0f b7 45 c4 movzwl -0x3c(%rbp),%eax 40067b: 83 c0 01 add $0x1,%eax 40067e: 66 89 45 c4 mov %ax,-0x3c(%rbp) 400682: bf 54 07 40 00 mov $0x400754,%edi 400687: e8 d4 fd ff ff callq 400460 <puts@plt> 40068c: 48 8b 45 b8 mov -0x48(%rbp),%rax 400690: 48 83 c0 08 add $0x8,%rax 400694: 8b 10 mov (%rax),%edx 400696: 83 c2 01 add $0x1,%edx 400699: 89 10 mov %edx,(%rax) 40069b: bf 58 07 40 00 mov $0x400758,%edi 4006a0: e8 bb fd ff ff callq 400460 <puts@plt> 4006a5: 8b 45 d8 mov -0x28(%rbp),%eax 4006a8: 83 c0 01 add $0x1,%eax 4006ab: 89 45 d8 mov %eax,-0x28(%rbp) 4006ae: b8 00 00 00 00 mov $0x0,%eax 4006b3: 48 8b 4d f8 mov -0x8(%rbp),%rcx 4006b7: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx 4006be: 00 00 4006c0: 74 05 je 4006c7 <main+0x131> 4006c2: e8 a9 fd ff ff callq 400470 <__stack_chk_fail@plt> 4006c7: c9 leaveq 4006c8: c3 retq 4006c9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
x86编译和反汇编
0000842c <main>: 842c: e92d4800 push {fp, lr} 8430: e28db004 add fp, sp, #4 8434: e24dd038 sub sp, sp, #56 ; 0x38 8438: e3a03000 mov r3, #0 843c: e50b3018 str r3, [fp, #-24] ; 0xffffffe8 8440: e3a03000 mov r3, #0 8444: e54b3014 strb r3, [fp, #-20] ; 0xffffffec 8448: e3a03001 mov r3, #1 844c: e54b3017 strb r3, [fp, #-23] ; 0xffffffe9 8450: e3a03002 mov r3, #2 8454: e54b3016 strb r3, [fp, #-22] ; 0xffffffea 8458: e24b3024 sub r3, fp, #36 ; 0x24 845c: e3a02000 mov r2, #0 8460: e5832000 str r2, [r3] 8464: e2833004 add r3, r3, #4 8468: e3a02000 mov r2, #0 846c: e5832000 str r2, [r3] 8470: e2833004 add r3, r3, #4 8474: e3a02000 mov r2, #0 8478: e1c320b0 strh r2, [r3] 847c: e2833002 add r3, r3, #2 8480: e3a03001 mov r3, #1 8484: e14b32b4 strh r3, [fp, #-36] ; 0xffffffdc 8488: e3a03002 mov r3, #2 848c: e14b32b2 strh r3, [fp, #-34] ; 0xffffffde 8490: e3a03003 mov r3, #3 8494: e14b32b0 strh r3, [fp, #-32] ; 0xffffffe0 8498: e24b3038 sub r3, fp, #56 ; 0x38 849c: e3a02000 mov r2, #0 84a0: e5832000 str r2, [r3] 84a4: e2833004 add r3, r3, #4 84a8: e3a02000 mov r2, #0 84ac: e5832000 str r2, [r3] 84b0: e2833004 add r3, r3, #4 84b4: e3a02000 mov r2, #0 84b8: e5832000 str r2, [r3] 84bc: e2833004 add r3, r3, #4 84c0: e3a02000 mov r2, #0 84c4: e5832000 str r2, [r3] 84c8: e2833004 add r3, r3, #4 84cc: e3a02000 mov r2, #0 84d0: e5832000 str r2, [r3] 84d4: e2833004 add r3, r3, #4 84d8: e3a03002 mov r3, #2 84dc: e50b3038 str r3, [fp, #-56] ; 0xffffffc8 84e0: e3a03003 mov r3, #3 84e4: e50b3034 str r3, [fp, #-52] ; 0xffffffcc 84e8: e3a03004 mov r3, #4 84ec: e50b3030 str r3, [fp, #-48] ; 0xffffffd0 84f0: e24b3018 sub r3, fp, #24 84f4: e50b3008 str r3, [fp, #-8] 84f8: e24b3024 sub r3, fp, #36 ; 0x24 84fc: e50b300c str r3, [fp, #-12] 8500: e24b3038 sub r3, fp, #56 ; 0x38 8504: e50b3010 str r3, [fp, #-16] 8508: e59f00b0 ldr r0, [pc, #176] ; 85c0 <main+0x194> 850c: ebffff8f bl 8350 <_init+0x20> 8510: e51b3008 ldr r3, [fp, #-8] 8514: e2833002 add r3, r3, #2 8518: e5d32000 ldrb r2, [r3] 851c: e2822001 add r2, r2, #1 8520: e20220ff and r2, r2, #255 ; 0xff 8524: e5c32000 strb r2, [r3] 8528: e59f0094 ldr r0, [pc, #148] ; 85c4 <main+0x198> 852c: ebffff87 bl 8350 <_init+0x20> 8530: e55b3016 ldrb r3, [fp, #-22] ; 0xffffffea 8534: e2833001 add r3, r3, #1 8538: e20330ff and r3, r3, #255 ; 0xff 853c: e54b3016 strb r3, [fp, #-22] ; 0xffffffea 8540: e59f0078 ldr r0, [pc, #120] ; 85c0 <main+0x194> 8544: ebffff81 bl 8350 <_init+0x20> 8548: e51b300c ldr r3, [fp, #-12] 854c: e2833004 add r3, r3, #4 8550: e1d320b0 ldrh r2, [r3] 8554: e2822001 add r2, r2, #1 8558: e1a02802 lsl r2, r2, #16 855c: e1a02822 lsr r2, r2, #16 8560: e1c320b0 strh r2, [r3] 8564: e59f0058 ldr r0, [pc, #88] ; 85c4 <main+0x198> 8568: ebffff78 bl 8350 <_init+0x20> 856c: e15b32b0 ldrh r3, [fp, #-32] ; 0xffffffe0 8570: e2833001 add r3, r3, #1 8574: e1a03803 lsl r3, r3, #16 8578: e1a03823 lsr r3, r3, #16 857c: e14b32b0 strh r3, [fp, #-32] ; 0xffffffe0 8580: e59f0038 ldr r0, [pc, #56] ; 85c0 <main+0x194> 8584: ebffff71 bl 8350 <_init+0x20> 8588: e51b3010 ldr r3, [fp, #-16] 858c: e2833008 add r3, r3, #8 8590: e5932000 ldr r2, [r3] 8594: e2822001 add r2, r2, #1 8598: e5832000 str r2, [r3] 859c: e59f0020 ldr r0, [pc, #32] ; 85c4 <main+0x198> 85a0: ebffff6a bl 8350 <_init+0x20> 85a4: e51b3030 ldr r3, [fp, #-48] ; 0xffffffd0 85a8: e2833001 add r3, r3, #1 85ac: e50b3030 str r3, [fp, #-48] ; 0xffffffd0 85b0: e3a03000 mov r3, #0 85b4: e1a00003 mov r0, r3 85b8: e24bd004 sub sp, fp, #4 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+4))++; } void test2(int *p) { p[4]++; } int main() { char *char_p, char_arr[5]={0,1,2}; short *short_p, short_arr[5]={1,2,3}; int *int_p, int_arr[5]={2,3,4}; 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 0; }
可以发现test1()、test2()反汇编实现是一样的, 不会因为“形式”上我用数组还是指针, 最终的本质是指针, 而调用无论传的是数组地址还是指针地址, 没有影响, 都是地址值而已
0000000000400596 <test1>: 400596: 55 push %rbp 400597: 48 89 e5 mov %rsp,%rbp 40059a: 48 89 7d f8 mov %rdi,-0x8(%rbp) 40059e: 48 8b 45 f8 mov -0x8(%rbp),%rax 4005a2: 48 83 c0 10 add $0x10,%rax 4005a6: 8b 10 mov (%rax),%edx 4005a8: 83 c2 01 add $0x1,%edx 4005ab: 89 10 mov %edx,(%rax) 4005ad: 90 nop 4005ae: 5d pop %rbp 4005af: c3 retq 00000000004005b0 <test2>: 4005b0: 55 push %rbp 4005b1: 48 89 e5 mov %rsp,%rbp 4005b4: 48 89 7d f8 mov %rdi,-0x8(%rbp) 4005b8: 48 8b 45 f8 mov -0x8(%rbp),%rax 4005bc: 48 83 c0 10 add $0x10,%rax 4005c0: 8b 10 mov (%rax),%edx 4005c2: 83 c2 01 add $0x1,%edx 4005c5: 89 10 mov %edx,(%rax) 4005c7: 90 nop 4005c8: 5d pop %rbp 4005c9: c3 retq 4006e7: e8 74 fd ff ff callq 400460 <puts@plt> 4006ec: 48 8b 45 b8 mov -0x48(%rbp),%rax 4006f0: 48 89 c7 mov %rax,%rdi 4006f3: e8 9e fe ff ff callq 400596 <test1> 4006f8: 48 8b 45 b8 mov -0x48(%rbp),%rax 4006fc: 48 89 c7 mov %rax,%rdi 4006ff: e8 ac fe ff ff callq 4005b0 <test2> 400704: bf e0 07 40 00 mov $0x4007e0,%edi 400709: e8 52 fd ff ff callq 400460 <puts@plt> 40070e: 48 8d 45 d0 lea -0x30(%rbp),%rax 400712: 48 89 c7 mov %rax,%rdi 400715: e8 7c fe ff ff callq 400596 <test1> 40071a: 48 8d 45 d0 lea -0x30(%rbp),%rax 40071e: 48 89 c7 mov %rax,%rdi 400721: 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+4))++; } void test2(int p[]) { p[4]++; }
反汇编的结果发现和上面函数形参是指针的一模一样! 也就是说虽然我的参数看起来像数组, 但实际上由于没有指定成员数量实际分配内存, 所以编译器还是把参数当做指针对待!
结论:
a. “真实”数组操作比指针高效
b. 有些看起来像数组, 实质是指针的, 指令走的还是指针那一套, 无论语法写的是*p++还是p[i]