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


180

В каком сегменте (.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.


1
Большинство людей говорят вам, что они должны храниться в разделе .DATA вместо того, чтобы отвечать на ваш вопрос: где именно в разделе .DATA и как вы можете найти где. Я вижу, вы уже отметили ответ, так что вы уже знаете, как его найти?
lukmac

почему инициализирован и неинициализированного размещены в различных разделах: linuxjournal.com/article/1059
МХК

1
Хранилище, выделенное для ваших глобальных / статических переменных во время выполнения, не имеет ничего общего с разрешением их имен, которое происходит во время сборки / компоновки. После того, как исполняемый файл был собран - больше нет имен.
Вальдо

2
Этот вопрос не имеет смысла, поскольку он основан на ложной предпосылке, что «коллизия имен» неисследованных символов - это вещь, которая может существовать. Тот факт, что нет законного вопроса, может объяснить, насколько ужасны некоторые ответы. Трудно поверить, так мало людей получили это.
underscore_d

Ответы:


131

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


50
«Инициализировано не 0» вы, вероятно, имеете в виду «инициализировано, но с чем-то отличным от 0». Потому что в C / C ++ нет такого понятия, как «неинициализированные» статические данные. Все статическое по умолчанию инициализируется нулями.
2010 года

21
@ Дон Нойфельд: ваш ответ не отвечает на вопрос вообще. Я не понимаю, почему это принято. Потому что и 'foo', и 'bar' не инициализированы 0. Вопрос в том, куда поместить две статические / глобальные переменные с одинаковыми именами в .bss или .data
lukmac

Я использовал реализации, в которые входили статические данные, которые были явно инициализированы нулями .data, и статические данные без инициализатора .bss.
MM

1
@MM В моем случае, является ли статический член неинициализированным (неявно инициализированным в 0) или явно инициализированным в 0, в обоих случаях он добавляется в раздел .bss.
cbinder

Эта информация относится к определенному типу исполняемых файлов? Я полагаю, поскольку вы не указали, что это относится по крайней мере к исполняемым файлам ELF и Windows PE, но как насчет других типов?
Джерри Иеремия

116

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

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

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

введите описание изображения здесь


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

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


Ответ выше говорит, что 0 инициализированных переходит в BSS. 0 инициализированный означает неинициализированный или 0 как таковой? Если это означает 0 само по себе, то я думаю, что вы должны включить его в свой ответ.
Вирадж

Постоянные данные хранятся не в сегменте .data, а в сегменте .const текстового раздела.
user10678

Вместо этого (« Сегмент инициализированных данных : здесь хранятся все глобальные, статические и постоянные данные. Сегмент неинициализированных данных (BSS) : все неинициализированные данные хранятся в этом сегменте».), Я думаю, что следует сказать следующее: (« Сегмент инициализированных данных : здесь хранятся все глобальные и статические переменные, которые были инициализированы ненулевым значением, и все постоянные данные. Сегмент неинициализированных данных (BSS) : все глобальные и статические переменные, которые НЕ были инициализированы или инициализированы в ноль, хранятся в этом сегменте. ").
Габриэль Стейплс

Также обратите внимание, что, насколько я понимаю, «инициализированные данные» могут состоять из инициализированных переменных и констант . На микроконтроллере (например, STM32) инициализированные переменные по умолчанию сохраняются во флэш- памяти и копируются в ОЗУ при запуске , а инициализированные константы остаются в памяти и предназначены для чтения только из Flash вместе с текстом , который содержит Сама программа, и остается только
Габриэль Стейплс

Итак, что я собираю из этой диаграммы, так это то, что переменные, которые являются глобальными или статическими (поскольку статические переменные действуют как глобальные переменные по продолжительности), не находятся ни в куче, ни в стеке, а скорее размещаются в памяти отдельно от обоих. Это правильно? Полагаю, я мог бы еще раз взглянуть на скрипт компоновщика STM32, чтобы еще больше изучить распределение памяти.
Габриэль Стейплз

32

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

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 (в зависимости от того, инициализирована она явно или нет, соответственно). Затем его масштабируют соответственно всем функциям или функциям в исходном файле.


21

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

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


нет. static keyworld имеет перегруженные значения: в таком случае static является модификатором хранилища, а не модификатором связи.
ugasoft

4
ugasoft: статика вне функции - модификаторы связи, внутри - модификаторы хранилища, где не может быть столкновений, с которых можно начинать.
Внуаз

12

в "глобальной и статичной" области :)

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

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

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

Следующее суммирует основные области памяти программы на C ++. Обратите внимание, что некоторые имена (например, «куча») не отображаются как таковые в проекте [стандарта].

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.

12

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

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

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

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


1
@paxdiablo: вы упомянули два типа статических переменных. К какой из них относится эта статья ( en.wikipedia.org/wiki/Data_segment )? Сегмент данных также содержит глобальные переменные (которые по своей природе абсолютно противоположны статическим). So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer

@eSKay, это связано с видимостью. В сегменте могут храниться вещи, которые являются локальными для модуля компиляции, а другие полностью доступны. Один пример: представьте, что каждый комп-блок вносит блок в сегмент DATA. Он знает, где все в этом блоке. Он также публикует адреса тех вещей в блоке, к которым он хочет, чтобы другие комп-модули имели доступ. Компоновщик может разрешить эти адреса во время ссылки.
paxdiablo

11

Как найти это самостоятельно objdump -Sr

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

Давайте проанализируем пример 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 -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


6

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


5

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

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

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


3

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

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


Существует гораздо больше, чем это. Даже существующие ответы не являются полными. Просто упомянуть кое-что еще: нить местных жителей.
Адриано Репетти

2

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


2

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


1

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


0

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


-1

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

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

void main(void)
{
static int i;
}

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.