静态局部变量存储在内存中的哪个位置?只能在声明它们的函数内访问局部变量。
全局静态变量进入.data段。
如果静态全局变量和静态局部变量的名称相同,编译器如何区分它们?
静态局部变量存储在内存中的哪个位置?只能在声明它们的函数内访问局部变量。
全局静态变量进入.data段。
如果静态全局变量和静态局部变量的名称相同,编译器如何区分它们?
静态变量与全局变量进入同一段。两者之间唯一不同的是编译器“隐藏”链接器中的所有静态变量:只有外部(全局)变量的名称才会被暴露。这就是编译器如何允许具有相同名称的静态变量存在于不同的转换单元中。静态变量的名称在编译阶段仍然是已知的,但随后它们的数据被放入 .data
匿名细分。
静态变量几乎与全局变量相似,因此未初始化的静态变量在BSS中,初始化的静态变量在数据段中。
正如dasblinken所提到的,GCC 4.8将局部静态与全局变量放在同一个地方。
更确切地说:
static int i = 0
继续 .bss
static int i = 1
继续 .data
让我们分析一个Linux x86-64 ELF示例来自己查看:
#include <stdio.h>
int f() {
static int i = 1;
i++;
return i;
}
int main() {
printf("%d\n", f());
printf("%d\n", f());
return 0;
}
为了得出结论,我们需要了解搬迁信息。如果您从未接触过,请考虑 先阅读这篇文章。
编译它:
gcc -ggdb -c main.c
用以下代码反编译代码:
objdump -S main.o
f
包含:
int f() {
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
static int i = 1;
i++;
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
a: 83 c0 01 add $0x1,%eax
d: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 13 <f+0x13>
return i;
13: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 19 <f+0x19>
}
19: 5d pop %rbp
1a: c3 retq
哪3次访问 i
:
4
搬到了 eax
准备增量d
将递增的值移回内存13
移动 i
到了 eax
为了返回值。从那以后显然是不必要的 eax
已经包含它,和 -O3
能够删除。所以我们只关注它 4
:
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
我们来看看重定位数据:
readelf -r main.o
其中说明了链接器在创建可执行文件时如何修改文本部分地址。
它包含:
Relocation section '.rela.text' at offset 0x660 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000300000002 R_X86_64_PC32 0000000000000000 .data - 4
我们看看 .rela.text
而不是其他人因为我们对重新安置感兴趣 .text
。
Offset 6
直接落入从字节4开始的指令:
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
^^
This is offset 6
根据我们对x86-64指令编码的了解:
8b 05
是个 mov
部分00 00 00 00
是地址部分,从字节开始 6
AMD64 System V ABI更新 告诉我们 R_X86_64_PC32
作用于4个字节(00 00 00 00
)并将地址计算为:
S + A - P
意思是:
S
:段指出: .data
A
: Added
: -4
P
:加载时字节6的地址-P
是需要的,因为GCC使用 RIP
相对地址,所以我们必须打折 .text
-4
需要因为 RIP
指向byte处的以下指令 0xA
但 P
是字节 0x6
,所以我们需要打折4。
结论:链接后它将指向的第一个字节 .data
分割。