静态局部变量存储在内存中的什么位置?局部变量只能在声明它们的函数内部访问。
全局静态变量进入 .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 Update告诉我们,它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
是 byte 0x6
,所以我们需要折扣 4。
结论:链接后它将指向.data
段的第一个字节。