Чому довгий int займає 12 байт на деяких машинах?


26

Я помітив щось дивне після складання цього коду на своїй машині:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

Результат такий. Зауважте, що між кожною адресою int існує 4-байтна різниця. Однак між останнім int та long int є різниця у 12 байтів:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88

3
Помістіть інше intпісля hу вихідний код. Компілятор може поставити його в пробіл раніше h.
ctrl-alt-delor

32
Не використовуйте різницю між адресами пам'яті для визначення розміру. Для цього є sizeofфункція. printf("size: %d ", sizeof(long));
Кріс Шнайдер

10
Ви друкуєте лише низькі 4 байти своїх адрес %x. На щастя, вам трапляється правильно працювати на вашій платформі, щоб передавати вказівні аргументи з очікуваним рядком формату unsigned int, але вказівники та вставки мають різні розміри у багатьох ABI. Використовуйте %pдля друку покажчиків у портативному коді. (Неважко уявити систему, де ваш код надрукував би верхню / нижню половинки перших 4 вказівників, а не нижню половину всіх 8.)
Пітер Кордес,

5
@ChrisSchneider для друку size_t використання%zu . @yoyo_fun для друку використання адрес%p . Використання неправильного специфікатора формату викликає невизначене поведінку
phuclv

2
@luu не поширювати дезінформацію. Жоден гідний компілятор не піклується про порядок, в якому змінні оголошуються в C. Якщо це стосується, немає жодної причини, чому він би робив це так, як ви описали.
gnasher729

Відповіді:


81

Це не зайняло 12 байтів, а лише 8. Однак вирівнювання за замовчуванням для 8-байтного int на цій платформі становить 8 байт. Таким чином, компілятору потрібно було перемістити довгий int на адресу, що ділиться на 8. "Очевидна" адреса, da54dc8c, не ділиться на 8, отже, розрив у 12 байт.

Ви повинні мати можливість це перевірити. Якщо ви додасте ще один int до довгого, тож їх є 8, ви повинні виявити, що довгий int буде вирівняний нормально без руху. Тепер це буде лише 8 байт від попередньої адреси.

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


3
різниця, а не розрив.
Дедуплікатор

10
"включаючи переупорядкування змінних". Якщо компілятор вирішить, що ви не використовуєте дві змінні одночасно, він також може частково перекриватись або повністю перекривати їх ...
Roger Lipscombe

8
Або, справді, зберігайте їх у реєстрах, а не на стеці.
Зупиніть шкодити Моніці

11
@OrangeDog Я не думаю, що це станеться, якщо адреса буде взята як у цьому випадку, але, як правило, ви правильні.
Олексій

5
@Alex: Ви можете отримати смішні речі з пам’яттю та реєстрами, приймаючи адресу. Якщо взяти адресу, це означає, що вона повинна надавати їй місце пам’яті, але не означає, що воно має фактично використовувати її. Якщо ви візьмете адресу, призначите їй 3 і передаєте її іншій функції, вона може просто записати 3 в RDI і зателефонувати, ніколи не записуючи її в пам'ять. Дивує іноді дибугер.
Зан Лінкс

9

Це тому, що ваш компілятор генерує додаткові прокладки між змінними, щоб переконатися, що вони правильно вирівняні в пам'яті.

У більшості сучасних процесорів, якщо значення має адресу, кратну його розміру, доступ до нього більш ефективний. Якби він був розміщений hна першому доступному місці, його адреса була б 0xda54dc8c, що не кратно 8, тому було б менш ефективно використовувати. Компілятор знає про це і додає трохи невикористаного простору між вашими останніми двома змінними, щоб переконатися, що це відбувається.


Дякую за пояснення. Чи можете ви вказати мені на деякі матеріали щодо причин, завдяки яким доступ до змінних, що кратні за своїми розмірами, є більш ефективним? я хотів би знати, чому це відбувається?
yoyo_fun

4
@yoyo_fun, і якщо ви дійсно хочете зрозуміти пам'ять, то є відомий документ на тему futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex

1
@yoyo_fun Це досить просто. Деякі контролери пам'яті можуть отримати доступ лише до декількох бітових ширин процесора (наприклад, 32-розрядний процесор може лише безпосередньо запитувати адреси 0-3, 4-7, 8-11 тощо). Якщо ви запитаєте нерівну адресу, процесор повинен зробити два запити на пам'ять, після чого введіть дані в реєстр. Отже, повертаючись до 32-бітного, якщо ви хочете, щоб значення, збережене за адресою 1, процесор повинен запитати адреси 0-3, 4-7, а потім отримати байти від 1, 2, 3 і 4. Чотири байти пам'ять, прочитану даремно.
фірфокс

2
Незначна точка, але неправильний доступ до пам’яті може бути невиправною помилкою замість того, щоб досягти продуктивності. Архітектура залежить.
Джон Честерфілд

1
@JonChesterfield - Так. Ось чому я прокоментував, що опис, який я дав, стосується більшості сучасних архітектур (під якими я здебільшого маю на увазі x86 та ARM). Є й інші, які поводяться по-різному, але вони істотно рідше. (Цікаво: ARM раніше була однією з архітектур, які потребували узгодженого доступу, але вони додали автоматичну обробку нестандартного доступу в наступних редакціях)
Jules

2

Ваш тест не обов'язково тестує те, що ви вважаєте, що це таке, оскільки не існує вимоги мови співвідносити адресу будь-якої з цих локальних змінних між собою.

Вам потрібно буде поставити ці поля як структуру, щоб можна було зробити висновок про розподіл пам’яті.

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

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

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