[c++] Где хранятся статические переменные (в C / C ++)?



Answers

Когда программа загружается в память, она организована в разные сегменты. Один из сегментов - сегмент DATA . Сегмент данных далее подразделяется на две части:

Инициализированный сегмент данных: здесь хранятся все глобальные, статические и постоянные данные.
Неинициализированный сегмент данных (BSS): все неинициализированные данные хранятся в этом сегменте.

Вот схема, объясняющая эту концепцию:


вот очень хорошая ссылка, объясняющая эти понятия:

http://www.inf.udec.cl/~leo/teoX.pdf

Question

В каком сегменте (.BSS, .DATA, other) исполняемого файла хранятся статические переменные, так что они не имеют конфликтов имен? Например:


foo.c:                         bar.c:
static int foo = 1;            static int foo = 10;
void fooTest() {               void barTest() {
  static int bar = 2;            static int bar = 20;
  foo++;                         foo++;
  bar++;                         bar++;
  printf("%d,%d", foo, bar);     printf("%d, %d", foo, bar);
}                              }

Если я скомпилирую оба файла и привяжу его к основному, который вызывает fooTest () и barTest несколько раз, операторы printf увеличиваются независимо. Имеет смысл, поскольку переменные foo и bar являются локальными для единицы перевода.

Но где выделено хранилище?

Чтобы быть ясным, предполагается, что у вас есть инструментальная цепочка, которая выводит файл в формате ELF. Таким образом, я считаю, что для этих статических переменных должно быть зарезервировано место в исполняемом файле.
В целях обсуждения предположим, что мы используем инструментальную цепочку GCC.




Как найти его с помощью objdump -Sr

Чтобы на самом деле понять, что происходит, вы должны понимать, что перемещение линкера. Если вы никогда не касались этого, сначала прочитайте этот пост .

Давайте проанализируем пример 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 -Sr main.o
  • -S декомпилирует код с исходным исходным перемещением
  • -r показывает информацию о перемещении

Внутри декомпиляции f мы видим:

 static int i = 1;
 i++;
4:  8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <f+0xa>
        6: R_X86_64_PC32    .data-0x4

и .data-0x4 говорит, что он перейдет к первому байту сегмента .data .

-0x4 есть, потому что мы используем относительную адресацию RIP, таким образом, %rip в команде и R_X86_64_PC32 .

Это необходимо, потому что RIP указывает на следующую инструкцию, которая начинается с 4 байт после 00 00 00 00 что и будет перенесено. Я объяснил это более подробно: https://.com/a/30515926/895245

Затем, если мы изменим источник на i = 1 и проведем тот же анализ, мы заключаем, что:

  • static int i = 0 продолжается .bss
  • static int i = 1 продолжается .data



Данные, объявленные в блоке компиляции, войдут в .BSS или .Data из этих файлов. Инициализированные данные в BSS, неинициализированные в DATA.

Разница между статическими и глобальными данными заключается в включении символьной информации в файл. Компиляторы обычно включают в себя информацию о символе, но только отмечают глобальную информацию как таковую.

Компонент уважает эту информацию. Информация о символах для статических переменных либо отбрасывается, либо искажается, поэтому статические переменные все равно могут быть связаны каким-либо образом (с вариантами отладки или символа). В любом случае не могут быть затронуты единицы компиляции, так как компоновщик сначала разрешает локальные ссылки.




Если статическая переменная инициализирована, то она будет храниться в памяти сегмента данных. Если статическая переменная неинициализирована (по умолчанию она равна нулю), то она хранится в BSS (блок, запущенный символом), который представляет собой неинтеллизированную память сегмента данных.




они оба будут сохранены независимо, однако, если вы хотите дать понять другим разработчикам, вы можете их обернуть в пространствах имен.




статическая переменная, хранящаяся в сегменте данных или сегменте кода, как упоминалось ранее.
Вы можете быть уверены, что он не будет выделен на стек или кучу.
Не существует риска столкновения, поскольку ключевое слово static определяет область действия переменной как файла или функции, в случае столкновения есть компилятор / компоновщик, чтобы предупредить вас.
Хороший example




Я попробовал это с objdump и gdb, вот результат, который я получаю:

(gdb) disas fooTest
Dump of assembler code for function fooTest:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: mov    0x200b09(%rip),%eax        # 0x601040 <foo>
   0x0000000000400537 <+10>:    add    $0x1,%eax
   0x000000000040053a <+13>:    mov    %eax,0x200b00(%rip)        # 0x601040 <foo>
   0x0000000000400540 <+19>:    mov    0x200afe(%rip),%eax        # 0x601044 <bar.2180>
   0x0000000000400546 <+25>:    add    $0x1,%eax
   0x0000000000400549 <+28>:    mov    %eax,0x200af5(%rip)        # 0x601044 <bar.2180>
   0x000000000040054f <+34>:    mov    0x200aef(%rip),%edx        # 0x601044 <bar.2180>
   0x0000000000400555 <+40>:    mov    0x200ae5(%rip),%eax        # 0x601040 <foo>
   0x000000000040055b <+46>:    mov    %eax,%esi
   0x000000000040055d <+48>:    mov    $0x400654,%edi
   0x0000000000400562 <+53>:    mov    $0x0,%eax
   0x0000000000400567 <+58>:    callq  0x400410 <printf@plt>
   0x000000000040056c <+63>:    pop    %rbp
   0x000000000040056d <+64>:    retq   
End of assembler dump.

(gdb) disas barTest
Dump of assembler code for function barTest:
   0x000000000040056e <+0>: push   %rbp
   0x000000000040056f <+1>: mov    %rsp,%rbp
   0x0000000000400572 <+4>: mov    0x200ad0(%rip),%eax        # 0x601048 <foo>
   0x0000000000400578 <+10>:    add    $0x1,%eax
   0x000000000040057b <+13>:    mov    %eax,0x200ac7(%rip)        # 0x601048 <foo>
   0x0000000000400581 <+19>:    mov    0x200ac5(%rip),%eax        # 0x60104c <bar.2180>
   0x0000000000400587 <+25>:    add    $0x1,%eax
   0x000000000040058a <+28>:    mov    %eax,0x200abc(%rip)        # 0x60104c <bar.2180>
   0x0000000000400590 <+34>:    mov    0x200ab6(%rip),%edx        # 0x60104c <bar.2180>
   0x0000000000400596 <+40>:    mov    0x200aac(%rip),%eax        # 0x601048 <foo>
   0x000000000040059c <+46>:    mov    %eax,%esi
   0x000000000040059e <+48>:    mov    $0x40065c,%edi
   0x00000000004005a3 <+53>:    mov    $0x0,%eax
   0x00000000004005a8 <+58>:    callq  0x400410 <printf@plt>
   0x00000000004005ad <+63>:    pop    %rbp
   0x00000000004005ae <+64>:    retq   
End of assembler dump.

вот результат objdump

Disassembly of section .data:

0000000000601030 <__data_start>:
    ...

0000000000601038 <__dso_handle>:
    ...

0000000000601040 <foo>:
  601040:   01 00                   add    %eax,(%rax)
    ...

0000000000601044 <bar.2180>:
  601044:   02 00                   add    (%rax),%al
    ...

0000000000601048 <foo>:
  601048:   0a 00                   or     (%rax),%al
    ...

000000000060104c <bar.2180>:
  60104c:   14 00                   adc    $0x0,%al

Итак, ваши четыре переменные находятся в событии раздела данных с тем же именем, но с другим смещением.




Место хранения данных будет зависящим от реализации.

Однако значение статики - это «внутренняя связь». Таким образом, этот символ является внутренним для модуля компиляции (foo.c, bar.c) и не может быть указан за пределами этого блока компиляции. Таким образом, конфликтов имен не может быть.




Related