4.2.4 x86: alloca() 函数

对alloca()函数并没有值得学习的。

该函数的作用像malloc()一样,但只会在栈上分配内存。

它分配的内存并不需要在函数结束时,调用像free()这样的函数来释放,当函数运行结束,ESP的值还原时,这部分内存会自动释放。对alloca()函数的实现也没有什么值得介绍的。

这个函数,如果精简一下,就是将ESP指针指向栈底,根据你所需要的内存大小将ESP指向所分配的内存块。让我们试一下:

  1. #include <malloc.h>
  2. #include <stdio.h>
  3. void f() {
  4. char *buf=(char*)alloca (600);
  5. _snprintf (buf, 600, "hi! %d, %d, %d\n", 1, 2, 3);
  6. puts (buf);
  7. };

(snprintf()函数作用与printf()函数基本相同,不同的地方是printf()会将结果输出到的标准输出stdout,snprintf()会将结果保存到内存中,后面两⾏代码可以使用printf()替换,但我想说明小内存的使用习惯。)

MSVC

让我们来编译 (MSVC 2010):

  1. ...
  2.  mov eax, 600 ; 00000258H
  3. call __alloca_probe_16
  4. mov esi, esp
  5.  push 3
  6. push 2
  7. push 1
  8. push OFFSET $SG2672
  9. push 600 ; 00000258H
  10. push esi
  11. call __snprintf
  12.  push esi
  13. call _puts
  14. add esp, 28 ; 0000001cH
  15. ...

这唯一的函数参数是通过EAX(未使用栈)传递。在函数调用结束时,ESP会指向 600字节的内存,我们可以像使用一般内存一样来使用它做为缓冲区。

GCC + Intel格式

GCC 4.4.1不需要调用函数就可以实现相同的功能:

  1. .LC0:
  2. .string "hi! %d, %d, %d\n"
  3. f:
  4. push ebp
  5. mov ebp, esp
  6. push ebx
  7. sub esp, 660
  8. lea ebx, [esp+39]
  9. and ebx, -16 ; align pointer by 16-bit border
  10. mov DWORD PTR [esp], ebx ; s
  11. mov DWORD PTR [esp+20], 3
  12. mov DWORD PTR [esp+16], 2
  13. mov DWORD PTR [esp+12], 1
  14. mov DWORD PTR [esp+8], OFFSET FLAT:.LC0 ; "hi! %d, %d, %d\n"
  15. mov DWORD PTR [esp+4], 600 ; maxlen
  16. call _snprintf
  17. mov DWORD PTR [esp], ebx
  18. call puts
  19. mov ebx, DWORD PTR [ebp-4]
  20. leave
  21. ret

### GCC + AT&T 格式

我们来看相同的代码,但使用了AT&T格式:

  1. .LC0:
  2. .string "hi! %d, %d, %d\n"
  3. f:
  4. pushl %ebp
  5. movl %esp, %ebp
  6. pushl %ebx
  7. subl $660, %esp
  8. leal 39(%esp), %ebx
  9. andl $-16, %ebx
  10. movl %ebx, (%esp)
  11. movl $3, 20(%esp)
  12. movl $2, 16(%esp)
  13. movl $1, 12(%esp)
  14. movl $.LC0, 8(%esp)
  15. movl $600, 4(%esp)
  16. call _snprintf
  17. movl %ebx, (%esp)
  18. call puts
  19. movl -4(%ebp), %ebx
  20. leave
  21. ret

代码与上面的那个图是相同的。

例如:movl $3, 20(%esp)与mov DWORD PTR [esp + 20],3是等价的,Intel的内存地址增加是使用register+offset,而AT&T使用的是offset(%register)。