Где статические локальные переменные

Где хранятся статические локальные переменные в памяти? Доступ к локальным переменным возможен только внутри функции, в которой они объявлены.

Глобальные статические переменные попадают в сегмент .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.