Где статические локальные переменные
Где хранятся статические локальные переменные в памяти? Доступ к локальным переменным возможен только внутри функции, в которой они объявлены.
Глобальные статические переменные попадают в сегмент .data.
Если имя статической глобальной и статической локальной переменной одинаково, то как компилятор их отличает?
Ответы
Ответ 1
Статические переменные переходят в тот же сегмент, что и глобальные переменные. Единственное, что отличается между ними, это то, что компилятор "скрывает" все статические переменные от компоновщика: только имена внешних (глобальных) переменных становятся видимыми. Таким образом, компиляторы позволяют статическим переменным с тем же именем существовать в разных единицах перевода. Имена статических переменных остаются известными на этапе компиляции, но затем их данные помещаются в сегмент .data
анонимно.
Ответ 2
Статическая переменная почти похожа на глобальную переменную и, следовательно, неинициализированная статическая переменная находится в BSS, а инициализированная статическая переменная находится в сегменте данных.
Ответ 3
Как упоминалось dasblinken, GCC 4.8 ставит локальную статику в том же месте, что и глобальные.
Точнее:
-
static int i = 0
продолжается .bss
-
static int i = 1
продолжается .data
Проанализируйте один пример ELF Linux x86-64, чтобы увидеть его сами:
#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
указывает на следующую команду в байте 0xA
, но P
является байтом 0x6
, поэтому нам нужно скинуть 4.
Заключение: после ссылки он укажет на первый байт сегмента .data
.