Где хранятся статические переменные в C и C++?

StackOverflow https://stackoverflow.com/questions/93039

  •  01-07-2019
  •  | 
  •  

Вопрос

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


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.

Это было полезно?

Решение

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

Другие советы

Когда программа загружается в память, она разбивается на различные сегменты.Один из сегментов Сегмент ДАННЫХ.Сегмент данных далее подразделяется на две части:

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

Вот диаграмма, поясняющая эту концепцию:

enter image description here


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

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

Фактически переменная представляет собой кортеж (хранилище, область действия, тип, адрес, значение):

storage     :   where is it stored, for example data, stack, heap...
scope       :   who can see us, for example global, local...
type        :   what is our type, for example int, int*...
address     :   where are we located
value       :   what is our value

Локальная область действия может означать локальную по отношению к единице трансляции (исходному файлу), функции или блоку, в зависимости от того, где она определена.Чтобы сделать переменную видимой для более чем одной функции, она обязательно должна находиться либо в области DATA, либо в области BSS (в зависимости от того, инициализирована она явно или нет соответственно).Затем его область действия соответственно распространяется либо на все функции, либо на функции в исходном файле.

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

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

Я не верю, что произойдет столкновение.Использование static на уровне файла (внешние функции) помечает переменную как локальную для текущего модуля компиляции (файла).Он никогда не виден за пределами текущего файла, поэтому ему не обязательно иметь имя.

Использование static внутри функции отличается от других: переменная видна только функции, просто ее значение сохраняется при вызовах этой функции.

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

При этом я считаю, что он будет храниться в DATA, который, как правило, имеет инициализированную переменную.Первоначально BSS обозначал byte-set-<something>, который содержал переменные, которые не были инициализированы.

Как найти его самостоятельно с помощью 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://stackoverflow.com/a/30515926/895245

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

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

в области «глобальных и статических» :)

в C++ есть несколько областей памяти

  • куча
  • бесплатный магазин
  • куча
  • глобальный и статический
  • константа

видеть здесь для подробного ответа на ваш вопрос

Это зависит от платформы и компилятора, которые вы используете.Некоторые компиляторы сохраняют данные непосредственно в сегменте кода.Статические переменные всегда доступны только для текущей единицы перевода, а имена не экспортируются, поэтому конфликты имен никогда не возникают.

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

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

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

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

Ну, этот вопрос слишком старый, но поскольку никто не указывает никакой полезной информации:Проверьте сообщение mohit12379, объясняющее хранение статических переменных с тем же именем в таблице символов:http://www.geekinterview.com/question_details/24745

Я попробовал это с помощью 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

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

Вот как (легко понять):

stack, heap and static data

Ответ вполне может зависеть от компилятора, поэтому вы, вероятно, захотите отредактировать свой вопрос (я имею в виду, что даже понятие сегментов не требуется ни ISO C, ни ISO C++).Например, в Windows исполняемый файл не имеет имен символов.Один 'foo' будет иметь смещение 0x100, другой, возможно, 0x2B0, и код обеих единиц перевода компилируется с учетом смещений для "их" foo.

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

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

давайте возьмем простой пример

void main(void)
{
static int i;
}

вышеуказанная статическая переменная не инициализируется, поэтому она переходит в неинициализированный сегмент данных (bss).

void main(void)
{
static int i=10;
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top