Де зберігаються статичні змінні в 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
mhk

1
Зберігання, виділене для ваших глобальних / статичних змінних під час виконання, не має нічого спільного з роздільною здатністю їх імен, що відбувається під час збирання / зв'язку. Після того, як виконується виконаний файл - імен більше немає.
valdo

2
Це питання є безглуздим, будується на хибній передумові, що "зіткнення імені" неекспортованих символів - це річ, яка може існувати. Той факт, що немає законного питання, може пояснити, наскільки страшні деякі відповіді. Важко повірити, що мало хто це отримав.
підкреслюй_d

Відповіді:


131

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


50
Під "не-0 ініціалізованим" ви, мабуть, маєте на увазі "ініціалізовано, але з чимось іншим, ніж 0". Тому що в C / C ++ немає такого поняття, як "неініціалізовані" статичні дані. Все статичне за замовчуванням ініціалізується нулем.
AnT

21
@Don Neufeld: ваша відповідь зовсім не відповідає на питання. Я не розумію, чому це прийнято. Тому що і "foo", і "bar" не ініціалізуються 0. Питання в тому, де розмістити дві статичні / глобальні змінні з тим самим іменем у .bss або .data
lukmac

Я використовував реалізацію, де входили статичні дані, явно ініціалізовані з нулем .data, і статичні дані без ініціалізатора .bss.
ММ

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 як таке, я думаю, ви повинні включити його у свою відповідь.
Viraj

Постійні дані зберігаються не в сегменті .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 має перевантажені значення: у такому випадку статичний є модифікатором пам’яті, а не модифікатором зв’язку.
ugasoft

4
ugasoft: статики поза функцією є модифікаторами зв'язку, всередині - модифікаторами зберігання, де не може виникнути зіткнення.
wnoise

12

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

У C ++ є кілька областей пам'яті:

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

Дивіться тут детальну відповідь на ваше запитання:

Далі узагальнено основні області пам'яті програми C ++. Зауважте, що деякі назви (наприклад, "heap") не відображаються як такі в проекті [стандарту].

     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

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

Використання статики всередині функції відрізняється - змінна видима лише для функції (статична чи ні), це лише її значення зберігається при викликах до цієї функції.

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

Сказавши це, я вважаю, що він буде зберігатися в 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)?
Лазер

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

11

Як знайти це самостійно 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


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.