Де в пам'яті зберігаються мої змінні в C?


156

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

Я думаю, що вони будуть розподілені так:

  • Глобальні змінні -------> дані
  • Статичні змінні -------> дані
  • Постійні типи даних -----> код
  • Локальні змінні (оголошені та визначені у функціях) --------> стек
  • Змінні, оголошені та визначені в головній функції -----> купи
  • Покажчики (наприклад, char *arr, int *arr) -------> кучного
  • Динамічно виділений простір (використовуючи malloc та calloc) --------> стек

Я маю на увазі ці змінні лише з точки зору С.

Будь ласка, виправте мене, якщо я помиляюся, оскільки я новачок у C.


4
Типи не зберігаються в пам'яті.

5
mainце лише інша функція. Змінні залишаються на стеці, якщо mallocтільки не було б, як і в інших місцях.
simonc

4
покажчики (як правило) зберігаються у стеці. Пам'ять, яку вони вказують (зазвичай виділяють через malloc / calloc), знаходиться (як правило) на купі.
jpm

3
динамічно виділений простір (використовуючи malloc, calloc) --------> купа
One Man Crew

3
змінні, оголошені та визначені в головній функції -----> стек
One Man Crew

Відповіді:


217

Ви маєте деякі з цих прав, але той, хто писав питання, обдурив вас хоча б одним питанням:

  • глобальні змінні -------> дані (правильні)
  • статичні змінні -------> дані (правильні)
  • постійні типи даних -----> код та / або дані. Розглянемо рядкові літерали для ситуації, коли сама константа буде зберігатися в сегменті даних, а посилання на неї будуть вбудовані в код
  • локальні змінні (оголошені та визначені у функціях) --------> стек (правильний)
  • змінні, оголошені та визначені у mainфункції -----> купи, також стека (вчитель намагався вас обдурити)
  • покажчики (напр .: char *arr, int *arr) -------> купа даних чи стек, залежно від контексту. C дозволяє вам оголосити глобальний або staticпокажчик, і в цьому випадку сам покажчик опиниться в сегменті даних.
  • динамічно розподіляє простір (використовуючи malloc, calloc, realloc) --------> стека купи

Варто згадати, що «стек» офіційно називається «класом автоматичного зберігання».


6
Також варто згадати, що купа офіційно взагалі нічого не називається. Виділена пам'ять походить звідкись, у стандарті для цього "десь" немає імені.
Стів Джессоп

6
У деяких системах (а саме Linux та * BSD) також існує allocaподібний до malloc, але розподіл стеків.
Андреас Грапентин

Куди піде змінна const, оголошена всередині методу?
Махорі

@Ravi Там же, де йдуть інші константи (точка №3 вище).
dasblinkenlight

Я використовую GCC 4.8.1 і, схоже, не зберігає змінну const, локальну до основної в сегменті DATA. Нижче наведено код та карту пам'яті для 3 таких програм: Код 1: int main (void) {// char a [10] = "HELLO"; // 1 // const char a [10] = "HELLO"; // 2 повернення 0; } ПАМ'ЯТНА КАРТА ЗА ВСІМ: текстові дані bss dec hex назва файлу 7264 1688 1040 9992 2708 a.exe ПАМ'ЯТКА КАРТА ДЛЯ 2: текстові дані bss dec hex filename 7280 1688 1040 10008 2718 a.exe ПАМЯТНА КАРТА ДЛЯ 3: текстові дані bss dec hex filename 7280 1688 1040 10008 2718 a.exe
Махорі

124

Для тих майбутніх відвідувачів, яким, можливо, буде цікаво дізнатися про ці сегменти пам'яті, я пишу важливі моменти про 5 сегментів пам'яті на С:

Деякі голови вгору:

  1. Щоразу, коли виконується програма C, в оперативній пам'яті виділяється деяка пам'ять для виконання програми. Ця пам'ять використовується для зберігання часто виконуваного коду (двійкових даних), програмних змінних тощо. Наведені нижче сегменти пам'яті говорять про те саме:
  2. Зазвичай існує три типи змінних:
    • Локальні змінні (їх також називають автоматичними змінними в C)
    • Глобальні змінні
    • Статичні змінні
    • Ви можете мати глобальні статичні або локальні статичні змінні, але вищевказані три є батьківськими типами.

5 сегментів пам'яті в C:

1. Сегмент коду

  • Сегмент коду, який також називають текстовим сегментом, - це область пам'яті, яка містить часто виконуваний код.
  • Сегмент коду часто використовується лише для читання, щоб уникнути ризику перекриття програмування помилок, таких як переповнення буфера тощо.
  • Сегмент коду не містить програмних змінних, таких як локальна змінна (її також називають автоматичними змінними на C ), глобальні змінні тощо.
  • На основі реалізації C сегмент коду може також містити рядкові літерали, доступні лише для читання. Наприклад, коли ви робите, printf("Hello, world")тоді в сегменті код / ​​текст створюється рядок "Привіт, світ". Ви можете перевірити це за допомогою sizeкоманди в ОС Linux.
  • Подальше читання

Сегмент даних

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

2. Неініціалізований сегмент даних

  • Цей сегмент також відомий як bss .
  • Це частина пам'яті, яка містить:
    1. Неініціалізовані глобальні змінні (включаючи вказівні змінні)
    2. Неініціалізовані постійні глобальні змінні .
    3. Неініціалізовані локальні статичні змінні .
  • Будь-яка глобальна або статична локальна змінна, яка не ініціалізована, буде зберігатися в неініціалізованому сегменті даних
  • Наприклад: глобальна змінна int globalVar;або статична локальна змінна static int localStatic;буде зберігатися в неініціалізованому сегменті даних.
  • Якщо ви оголосите глобальну змінну та ініціалізуєте її як 0або NULLтоді все-таки вона перейде до неініціалізованого сегмента даних або bss.
  • Подальше читання

3. Ініціалізований сегмент даних

  • У цьому сегменті зберігаються:
    1. Ініціалізовані глобальні змінні (включаючи вказівні змінні)
    2. Ініціалізовані постійні глобальні змінні .
    3. Ініціалізовані локальні статичні змінні .
  • Наприклад: глобальна змінна int globalVar = 1;або статична локальна змінна static int localStatic = 1;буде зберігатися в ініціалізованому сегменті даних.
  • Цей сегмент можна додатково класифікувати на ініціалізовану область лише для читання та ініціалізовану область читання-запису .Ініціалізовані постійні глобальні змінні будуть переходити в ініціалізовану область лише для читання, тоді як змінні, значення яких можна змінити під час виконання, будуть йти в ініціалізовану область читання-запису .
  • Розмір цього сегмента визначається розміром значень у вихідному коді програми і не змінюється під час виконання .
  • Подальше читання

4. Сегмент стека

  • Сегмент стека використовується для зберігання змінних, створених всередині функцій ( функція може бути основною функцією або визначеною користувачем функцією ), змінної типу
    1. Локальні змінні функції (включаючи змінні вказівника)
    2. Аргументи передані у функцію
    3. Зворотня адреса
  • Змінні, що зберігаються в стеку, будуть видалені, як тільки виконання функції закінчиться.
  • Подальше читання

5. Куповий сегмент

  • Цей сегмент повинен підтримувати динамічне розподіл пам'яті. Якщо програміст хоче виділити пам'ять динамічно , то в C це робиться з використанням malloc, callocабо reallocметодів.
  • Наприклад, коли int* prt = malloc(sizeof(int) * 2)тоді вісім байтів буде виділено в купу і адреса пам'яті цього місця буде повернута і збережена в ptrзмінній. TheptrЗмінний будуть або на стек даних або сегменти в залежності від того, як вона оголошена / використовуються.
  • Подальше читання

Чи не слід це ініціалізувати замість неініціалізованого у 3. Ініціалізований сегмент даних.
Сурай Джайн

Повторно "зберігається в неініціалізованому сегменті даних" (кілька екземплярів): Ви маєте на увазі "збережений неініціалізований у сегменті даних" ?
Пітер Мортенсен

@PeterMortensen Я маю на увазі обидва речі. "Будь-яка глобальна або статична локальна змінна, яка не ініціалізована, зберігатиметься в неініціалізованому сегменті даних"
hagrawal

як ми можемо мати глобальну статичну змінну в C?

під "деякими головами вгору" я знайшов цей пункт "Ви можете мати глобальні статичні або локальні статичні змінні, але вищевказані три - це батьківські типи". в якому u посилався на термін "глобальна статична". Моя точка - статична змінна не може бути глобальною. тобто, якщо будь-яка змінна повинна бути глобальною, вона повинна бути доступною до завершення виконання програми. Будь-ласка, поясніть та допоможіть, якщо я помиляюся.

11

Виправили неправильні речення

constant data types ----->  code //wrong

локальні постійні змінні -----> стек

ініціалізований глобальний постійний змінний -----> сегмент даних

неініціалізована глобальна константа змінної -----> bss

variables declared and defined in main function  ----->  heap //wrong

змінні, оголошені та визначені в головній функції -----> стека

pointers(ex:char *arr,int *arr) ------->  heap //wrong

dynamically allocated space(using malloc,calloc) --------> stack //wrong

покажчики (наприклад: char * arr, int * arr) -------> розмір цієї змінної вказівника буде в стеку.

Подумайте, що ви розподіляєте пам'ять з n байтів (використовуючи mallocабо calloc) динамічно, а потім робите змінну вказівника, щоб вказати на неї. Тепер, коли nбайти пам'яті знаходяться в купі, і змінна вказівника вимагає 4 байти (якщо 64-бітна машина 8 байт), яка буде в стеці, щоб зберігати початковий покажчикn байтів фрагмента пам'яті.

Примітка. Змінні вказівника можуть вказувати на пам'ять будь-якого сегмента.

int x = 10;
void func()
{
int a = 0;
int *p = &a: //Now its pointing the memory of stack
int *p2 = &x; //Now its pointing the memory of data segment
chat *name = "ashok" //Now its pointing the constant string literal 
                     //which is actually present in text segment.
char *name2 = malloc(10); //Now its pointing memory in heap
...
}

динамічно виділений простір (за допомогою malloc, calloc) --------> купи


покажчики можуть бути або в стеці, або в купі (див. особливо: покажчики на покажчики)
аргументація

@airza: Зараз оновлено. Насправді я оновлював лише ці деталі :)
rashok

На наступній карті пам'яті, будь ласка, вкажіть, де знаходиться стек та купа? Я не впевнений, що це правильне питання, оскільки стек та пам'ять можуть бути застосовні лише під час виконання. КАРТА ПАМ'ЯТІ: "текстові дані bss dec hex filename 7280 1688 1040 10008 2718 a.exe"
Mahori

7

Популярна архітектура робочого столу розділяє віртуальну пам’ять процесу на кілька сегментів :

  • Текстовий сегмент: містить виконуваний код. Покажчик інструкцій приймає значення в цьому діапазоні.

  • Сегмент даних: містить глобальні змінні (тобто об'єкти зі статичним зв’язком). Підрозділяються на дані лише для читання (наприклад, рядкові константи) та неініціалізовані дані ("BSS").

  • Сегмент стека: містить динамічну пам'ять для програми, тобто вільний запас ("купа") та локальні кадри стека для всіх потоків. Традиційно стек C і купа використовувались для того, щоб переростати в сегмент стека з протилежних кінців, але я вважаю, що практика була відмовлена, оскільки вона занадто небезпечна.

Програма змінного струму зазвичай розміщує об'єкти зі статичною тривалістю зберігання у сегменті даних, динамічно виділені об'єкти у вільному сховищі та автоматичні об’єкти у стеку виклику потоку, у якому він живе.

На інших платформах, таких як старий реальний режим x86 або на вбудованих пристроях, речі очевидно можуть бути кардинально різними.


"Я вважаю, що від практики відмовилися, оскільки вона занадто небезпечна" - і унеможливлює реалізацію потоків, оскільки тоді вам потрібно більше одного стека на програму, і вони не можуть бути в кінці :-)
Стів Джессоп

@SteveJessop: Так, я теж думав про це. Але нитки існують давно - я не знаю, чи всі стеки ниток також зростали назад, чи вони виростали б, як купу ... так чи інакше, сьогодні все йде в тому ж напрямку і є охорона сторінок.
Керрек СБ

6

Я маю на увазі ці змінні лише з точки зору С.

З точки зору мови С , все важливе - це ступінь, масштаби, зв'язок та доступ; як саме елементи відображаються в різних сегментах пам'яті, залежить від індивідуальної реалізації, і це буде різнитися. Стандартна мова не говорити про сегментах пам'яті у всіх . Більшість сучасних архітектур діють здебільшого однаково; Змінні блок-області та аргументи функцій будуть розподілятися із стека, файлові області та статичні змінні будуть виділятися з сегменту даних або коду, динамічна пам’ять буде виділена з купи, деякі постійні дані зберігатимуться в сегментах лише для читання тощо.


3

Одне, що потрібно пам’ятати про сховище, - це як правило . Компілятору не потрібно розміщувати змінну в певному місці - натомість він може розміщувати її куди завгодно до тих пір, поки складена програма поводиться так, ніби вона запускається на абстрактній машині С згідно з правилами абстрактної машини C. Це відноситься до всіх зберігання тривалостей . Наприклад:

  • змінна, до якої не можна отримати доступ до всіх, може бути повністю усунена - вона не має місця зберігання ... ніде. Приклад - подивіться, як існує 42у створеному коді збірки, але немає ознак 404.
  • змінна з автоматичною тривалістю зберігання, яка не приймає свою адресу, зовсім не повинна зберігатися в пам'яті. Прикладом може бути змінна цикл.
  • змінна, яка є constабо ефективно constне потребує пам'яті. Приклад - компілятор може довести, що fooце ефективно, constі вказує його використання в код. barмає зовнішню зв'язок, і компілятор не може довести, що вона не була б змінена поза поточним модулем, отже, вона не накреслена.
  • об'єкт, виділений з mallocнеобхідністю, не повинен перебувати в пам'яті, виділеній з купи! Приклад - зауважте, як у коді немає дзвінкаmalloc і значення 42 ніколи не зберігається в пам'яті, воно зберігається в реєстрі!
  • таким чином об'єкт, який був виділений, mallocі посилання втрачається, не розмовляючи об'єкт з free необхідністю не протікати пам'ять ...
  • Об'єкт, виділений за mallocнеобхідністю, не повинен знаходитися в межах під програмою break ( sbrk(0)) на Unixen ...

1

покажчики (напр .: char * arr, int * arr) -------> купа

Ні, вони можуть бути в стеці або в сегменті даних. Вони можуть вказувати куди завгодно.


Твердження про mainта динамічно розподілені змінні теж помиляються
simonc

Не тільки на стеці або сегменті даних. Подумайте про вказівник, який вказує на масив покажчиків. У цьому випадку покажчики в масиві зберігаються на купі.
Sebi2020

1
  • Змінні / автоматичні змінні ---> розділ стека
  • Динамічно розподілені змінні ---> розділ купи
  • Ініціалізовані глобальні змінні -> розділ даних
  • Неініціалізовані глобальні змінні -> розділ даних (bss)
  • Статичні змінні -> розділ даних
  • Строкові константи -> текстовий розділ / розділ коду
  • Функції -> текстовий розділ / розділ коду
  • Текстовий код -> текстовий розділ / розділ коду
  • Реєстри -> регістри процесора
  • Введення командного рядка -> середовище / розділ командного рядка
  • Екологічні змінні -> середовище / розділ командного рядка

Що таке розділ екологічного / командного рядка? Чи існують вони в Linux?
Haoyuan Ge

-1

Приклади мінімального запуску для Linux з аналізом демонтажу

Оскільки це детальна інформація про реалізацію, не визначена стандартами, давайте просто подивимось, що компілятор робить для певної реалізації.

У цій відповіді я або посилаюсь на конкретні відповіді, які роблять аналіз, або надам аналіз тут, і підсумую тут усі результати.

Усі вони є у різних версіях Ubuntu / GCC, і результати, ймовірно, досить стабільні у різних версіях, але якщо ми знайдемо будь-які варіанти, давайте уточнимо більш точні версії.

Локальна змінна всередині функції

Будь то mainчи будь-яка інша функція:

void f(void) {
    int my_local_var;
}

Як показано на: Що означає <значення, оптимізоване> в gdb?

  • -O0: стек
  • -O3: реєструється, якщо вони не розтікаються, складіть інакше

Для мотивації того, чому такий стек існує, див.: Яка функція інструкцій push / pop, що використовуються в регістрах у складі x86?

Глобальні змінні та staticфункціональні змінні

/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;

/* DATA */
int my_global_implicit_explicit_1 = 1;

void f(void) {
    /* BSS */
    static int my_static_local_var_implicit;
    static int my_static_local_var_explicit_0 = 0;

    /* DATA */
    static int my_static_local_var_explicit_1 = 1;
}
  • якщо ініціалізовано до 0або не ініціалізовано (і тому неявно ініціалізовано в 0): .bssрозділ, див. також: Для чого потрібен сегмент .bss?
  • інакше: .dataрозділ

char * і char c[]

Як показано на: Де зберігаються статичні змінні в C і C ++?

void f(void) {
    /* RODATA / TEXT */
    char *a = "abc";

    /* Stack. */
    char b[] = "abc";
    char c[] = {'a', 'b', 'c', '\0'};
}

Чи дуже великі літеральні рядки TODO також будуть поставлені на стек? Або .data? Або компіляція провалюється?

Аргументи функції

void f(int i, int j);

Потрібно пройти відповідну конвенцію про дзвінки, наприклад: https://en.wikipedia.org/wiki/X86_calling_conventions для X86, яка визначає конкретні регістри або місця стеку для кожної змінної.

Тоді як показано в розділі Що означає <значення оптимізованого> в gdb? , -O0потім кладе все в стек, -O3намагаючись максимально використовувати регістри.

Якщо функція стає вбудованою, до них звертаються так само, як до звичайних місцевих жителів.

const

Я вважаю, що це не має ніякого значення, тому що ви можете набрати це.

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

Аналіз TODO.

Покажчики

Вони є змінними (містять адреси, які є цифрами), так само, як і всі інші :-)

малок

Питання не має великого сенсу malloc, оскільки mallocце функція і в:

int *i = malloc(sizeof(int));

*i є змінною, яка містить адресу, тому вона потрапляє у вищезазначений випадок.

Що стосується того, як malloc працює всередині нього, коли ви називаєте його, ядро ​​Linux позначає певні адреси як записувані у своїх внутрішніх структурах даних, і коли їх програма торкається спочатку, відбувається помилка, і ядро ​​дозволяє сторінки таблиці, що дозволяє отримати доступ відбувається без segfaul: Як працює підказка x86?

Однак зауважте, що це в основному саме те, що execробить syscall під кришкою, коли ви намагаєтеся виконати виконуваний файл: він позначає сторінки, на які хоче завантажити, і записує туди програму, дивіться також: Як ядро ​​отримує виконуваний бінарний файл під управлінням linux? За винятком того, що execє деякі додаткові обмеження щодо того, куди слід завантажувати (наприклад, код не переміщується ).

Точний системний виклик використовується для mallocце mmapв сучасних реалізаціях 2020 року, а в минулому brkбув використаний: Чи має Танос () використання битий () або ММАП ()?

Динамічні бібліотеки

В основному, mmapредагуйте пам'ять: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710

екологічні змінні та mainsargv

Над початковим стеком: /unix/75939/where-is-the-environment-string-actual-stored TODO, чому б не в .data?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.