64.8 使用指针的函数参数
…更有意思的是,有可能在程序中,取一个函数参数的指针并将其传递给另外一个函数。
#include <stdio.h>
// located in some other file
void modify_a (int *a);
void f (int a)
{
modify_a (&a);
printf ("%d\n", a);
};
很难理解它是如果实现的,直到我们看到它的反汇编码:
Listing 64.10: Optimizing MSVC 2010
$SG2796 DB '%d', 0aH, 00H
_a$ = 8
_f PROC
lea eax, DWORD PTR _a$[esp-4] ; just get the address of value in local stack
push eax ; and pass it to modify_a()
call _modify_a
mov ecx, DWORD PTR _a$[esp] ; reload it from the local stack
push ecx ; and pass it to printf()
push OFFSET $SG2796 ; '%d'
call _printf
add esp, 12
ret 0
_f ENDP
传递到另一个函数是a在栈空间上的地址,该函数修改了指针指向的值然后再调用printf()来打印出修改之后的值。
细心的读者可能会问,使用寄存器传参的调用约定是如何传递函数指针参数的?
这是一种利用了影子空间的情况,输入的参数值先从寄存器复制到局部栈中的影子空间,然后再讲这个地址传递给其他函数。
Listing 64.11: Optimizing MSVC 2012 x64
$SG2994 DB '%d', 0aH, 00H
a$ = 48
f PROC
mov DWORD PTR [rsp+8], ecx ; save input value in Shadow Space
sub rsp, 40
lea rcx, QWORD PTR a$[rsp] ; get address of value and pass it to modify_a()
call modify_a
mov edx, DWORD PTR a$[rsp] ; reload value from Shadow Space and pass it to printf()
lea rcx, OFFSET FLAT:$SG2994 ; '%d'
call printf
add rsp, 40
ret 0
f ENDP
GCC同样将传入的参数存储在本地栈空间:
Listing 64.12: Optimizing GCC 4.9.1 x64
.LC0:
.string "%d\n"
f:
sub rsp, 24
mov DWORD PTR [rsp+12], edi ; store input value to the local stack
lea rdi, [rsp+12] ; take an address of the value and pass it to modify_a()
call modify_a
mov edx, DWORD PTR [rsp+12] ; reload value from the local stack and pass it to printf()
mov esi, OFFSET FLAT:.LC0 ; '%d'
mov edi, 1
xor eax, eax
call __printf_chk
add rsp, 24
ret
ARM64的GCC也做了同样的事情,但这个空间称为寄存器保护区:
f:
stp x29, x30, [sp, -32]!
add x29, sp, 0 ; setup FP
add x1, x29, 32 ; calculate address of variable in Register Save Area
str w0, [x1,-4]! ; store input value there
mov x0, x1 ; pass address of variable to the modify_a()
bl modify_a
ldr w1, [x29,28] ; load value from the variable and pass it to printf()
adrp x0, .LC0 ; '%d'
add x0, x0, :lo12:.LC0
bl printf ; call printf()
ldp x29, x30, [sp], 32
ret
.LC0:
.string "%d\n"
顺便提一下,一个类似影子空间的使用在这里也被提及过(46.1.2)。 #65章 线程局部存储
TLS是每个线程特有的数据区域,每个线程可以把自己需要的数据存储在这里。一个著名的例子是C标准的全局变量errno。多个线程可以同时使用errno获取返回的错误码,如果是全局变量它是无法在多线程环境下正常工作的。因此errno必须保存在TLS。
C++11标准里面新添加了一个thread_local修饰符,标明每个线程都属于自己版本的变量。它可以被初始化并位于TLS中。
Listing 65.1: C++11
#include <iostream>
#include <thread>
thread_local int tmp=3;
int main()
{
std::cout << tmp << std::endl;
};
使用MinGW GCC 4.8.1而不是MSVC2012编译。
如果我们查看它的PE文件,可以看到tmp变量被放到TLS section。