16.1 小例子

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a[20];
  5. int i;
  6. for (i=0; i<20; i++)
  7. a[i]=i*2;
  8. for (i=0; i<20; i++)
  9. printf ("a[%d]=%d", i, a[i]);
  10. return 0;
  11. };

16.1.1 x86

编译后:

Listing 16.1: MSVC

  1. _TEXT SEGMENT
  2. _i$ = -84 ; size = 4
  3. _a$ = -80 ; size = 80
  4. _main PROC
  5. push ebp
  6. mov ebp, esp
  7. sub esp, 84 ; 00000054H
  8. mov DWORD PTR _i$[ebp], 0
  9. jmp SHORT $LN6@main
  10. $LN5@main:
  11. mov eax, DWORD PTR _i$[ebp]
  12. add eax, 1
  13. mov DWORD PTR _i$[ebp], eax
  14. $LN6@main:
  15. cmp DWORD PTR _i$[ebp], 20 ; 00000014H
  16. jge SHORT $LN4@main
  17. mov ecx, DWORD PTR _i$[ebp]
  18. shl ecx, 1
  19. mov edx, DWORD PTR _i$[ebp]
  20. mov DWORD PTR _a$[ebp+edx*4], ecx
  21. jmp SHORT $LN5@main
  22. $LN4@main:
  23. mov DWORD PTR _i$[ebp], 0
  24. jmp SHORT $LN3@main
  25. $LN2@main:
  26. mov eax, DWORD PTR _i$[ebp]
  27. add eax, 1
  28. mov DWORD PTR _i$[ebp], eax
  29. $LN3@main:
  30. cmp DWORD PTR _i$[ebp], 20 ; 00000014H
  31. jge SHORT $LN1@main
  32. mov ecx, DWORD PTR _i$[ebp]
  33. mov edx, DWORD PTR _a$[ebp+ecx*4]
  34. push edx
  35. mov eax, DWORD PTR _i$[ebp]
  36. push eax
  37. push OFFSET $SG2463
  38. call _printf
  39. add esp, 12 ; 0000000cH
  40. jmp SHORT $LN2@main
  41. $LN1@main:
  42. xor eax, eax
  43. mov esp, ebp
  44. pop ebp
  45. ret 0
  46. _main ENDP

这段代码主要有两个循环:第一个循环填充数组,第二个循环打印数组元素。shl ecx,1指令使ecx的值乘以2,更多关于左移请参考17.3.1。 在堆栈上为数组分配了80个字节的空间,包含20个元素,每个元素4字节大小。

GCC 4.4.1编译后为:

Listing 16.2: GCC 4.4.1

  1. public main
  2. main proc near ; DATA XREF: _start+17
  3. var_70 = dword ptr -70h
  4. var_6C = dword ptr -6Ch
  5. var_68 = dword ptr -68h
  6. i_2 = dword ptr -54h
  7. i = dword ptr -4
  8. push ebp
  9. mov ebp, esp
  10. and esp, 0FFFFFFF0h
  11. sub esp, 70h
  12. mov [esp+70h+i], 0 ; i=0
  13. jmp short loc_804840A
  14. loc_80483F7:
  15. mov eax, [esp+70h+i]
  16. mov edx, [esp+70h+i]
  17. add edx, edx ; edx=i*2
  18. mov [esp+eax*4+70h+i_2], edx
  19. add [esp+70h+i], 1 ; i++
  20. loc_804840A:
  21. cmp [esp+70h+i], 13h
  22. jle short loc_80483F7
  23. mov [esp+70h+i], 0
  24. jmp short loc_8048441
  25. loc_804841B:
  26. mov eax, [esp+70h+i]
  27. mov edx, [esp+eax*4+70h+i_2]
  28. mov eax, offset aADD ; "a[%d]=%d
  29. "
  30. mov [esp+70h+var_68], edx
  31. mov edx, [esp+70h+i]
  32. mov [esp+70h+var_6C], edx
  33. mov [esp+70h+var_70], eax
  34. call _printf
  35. add [esp+70h+i], 1
  36. loc_8048441:
  37. cmp [esp+70h+i], 13h
  38. jle short loc_804841B
  39. mov eax, 0
  40. leave
  41. retn
  42. main endp

顺便提一下,一个int类型(指向int的指针)的变量—你可以使该变量指向数组并将该数组传递给另一个函数,更准确的说,传递的指针指向数组的第一个元素(该数组其它元素的地址需要显示计算)。比如a[idx],idx加上指向该数组的指针并返回该元素。 一个有趣的例子:类似”string”字符数组的类型是const char,索引可以应用与该指针。比如可能写作”string”[i]—正确的C/C++表达式。

16.1.2 ARM + Non-optimizing Keil + ARM mode

  1. EXPORT _main
  2. _main
  3. STMFD SP!, {R4,LR}
  4. SUB SP, SP, #0x50 ; allocate place for 20 int variables
  5. ; first loop
  6. MOV R4, #0 ; i
  7. B loc_4A0
  8. loc_494
  9. MOV R0, R4,LSL#1 ; R0=R4*2
  10. STR R0, [SP,R4,LSL#2] ; store R0 to SP+R4<<2 (same as SP+R4*4)
  11. ADD R4, R4, #1 ; i=i+1
  12. loc_4A0
  13. CMP R4, #20 ; i<20?
  14. BLT loc_494 ; yes, run loop body again
  15. ; second loop
  16. MOV R4, #0 ; i
  17. B loc_4C4
  18. loc_4B0
  19. LDR R2, [SP,R4,LSL#2] ; (second printf argument) R2=*(SP+R4<<4) (same as *(SP+R4*4))
  20. MOV R1, R4 ; (first printf argument) R1=i
  21. ADR R0, aADD ; "a[%d]=%d
  22. "
  23. BL __2printf
  24. ADD R4, R4, #1 ; i=i+1
  25. loc_4C4
  26. CMP R4, #20 ; i<20?
  27. BLT loc_4B0 ; yes, run loop body again
  28. MOV R0, #0 ; value to return
  29. ADD SP, SP, #0x50 ; deallocate place, allocated for 20 int variables
  30. LDMFD SP!, {R4,PC}

int类型长度为32bits即4字节,20个int变量需要80(0x50)字节,因此“sub sp,sp,#0x50”指令为在栈上分配存储空间。 两个循环迭代器i被存储在R4寄存器中。 值i2被写入数组,通过将i值左移1位实现乘以2的效果,整个过程通过”MOV R0,R4,LSL#1指令来实现。 “STR R0, [SP,R4,LSL#2]”把R0内容写入数组。过程为:SP指向数组开始,R4是i,i左移2位相当于乘以4,即(SP+R44)=R0。 第二个loop的“LDR R2, [SP,R4,LSL#2]”从数组读取数值到寄存器,R2=(SP+R4*4)。

16.1.3 ARM + Keil + thumb 模式优化后

  1. _main
  2. PUSH {R4,R5,LR}
  3. ; allocate place for 20 int variables + one more variable
  4. SUB SP, SP, #0x54
  5. ; first loop
  6. MOVS R0, #0 ; i
  7. MOV R5, SP ; pointer to first array element
  8. loc_1CE
  9. LSLS R1, R0, #1 ; R1=i<<1 (same as i*2)
  10. LSLS R2, R0, #2 ; R2=i<<2 (same as i*4)
  11. ADDS R0, R0, #1 ; i=i+1
  12. CMP R0, #20 ; i<20?
  13. STR R1, [R5,R2] ; store R1 to *(R5+R2) (same R5+i*4)
  14. BLT loc_1CE ; yes, i<20, run loop body again
  15. ; second loop
  16. MOVS R4, #0 ; i=0
  17. loc_1DC
  18. LSLS R0, R4, #2 ; R0=i<<2 (same as i*4)
  19. LDR R2, [R5,R0] ; load from *(R5+R0) (same as R5+i*4)
  20. MOVS R1, R4
  21. ADR R0, aADD ; "a[%d]=%d
  22. "
  23. BL __2printf
  24. ADDS R4, R4, #1 ; i=i+1
  25. CMP R4, #20 ; i<20?
  26. BLT loc_1DC ; yes, i<20, run loop body again
  27. MOVS R0, #0 ; value to return
  28. ; deallocate place, allocated for 20 int variables + one more variable
  29. ADD SP, SP, #0x54
  30. POP {R4,R5,PC}

Thumb代码也是非常类似的。Thumb模式计算数组偏移的移位操作使用特定的指令LSLS。 编译器在堆栈中申请的数组空间更大,但是最后4个字节的空间未使用。