Почему long 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 и длинным int существует 12-байтовая разница:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
источник
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. Тем не менее, выравнивание по умолчанию для int длиной 8 байтов на этой платформе составляет 8 байтов. Таким образом, компилятору нужно было переместить long int на адрес, который делится на 8. «Очевидный» адрес, da54dc8c, не делится на 8, следовательно, 12-байтовый пробел.

Вы должны быть в состоянии проверить это. Если вы добавите еще один int до long, то есть их будет 8, вы должны обнаружить, что long int будет выровнен нормально, без движения. Теперь это будет только 8 байтов от предыдущего адреса.

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

Alex
источник
3
Разница, а не разрыв.
Дедупликатор
10
«включая переменные упорядочения». Если компилятор решит, что вы не используете две переменные одновременно, он может частично или частично перекрывать или полностью перекрывать их ...
Роджер Липскомб
8
Или, действительно, храните их в регистрах, а не в стеке.
Стоп Harm Моника
11
@OrangeDog Я не думаю, что это произойдет, если адрес будет взят, как в этом случае, но, в общем, вы, конечно, правы.
Алекс
5
@ Алекс: Вы можете получить забавные вещи с памятью и регистрами, беря адрес. Взятие адреса означает, что оно должно дать ему место в памяти, но не означает, что оно должно фактически использовать его. Если вы берете адрес, назначаете ему 3 и передаете его другой функции, он может просто записать 3 в RDI и вызвать, никогда не записывая его в память. Удивительно иногда в отладчике.
Zan Lynx
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. Четыре байта память читается впустую.
phyrfox
2
Незначительный момент, но неправильный доступ к памяти может быть неисправимой ошибкой вместо снижения производительности. Архитектура зависит.
Джон Честерфилд
1
@JonChesterfield - Да. Вот почему я прокомментировал, что приведенное мной описание относится к большинству современных архитектур (под которыми я в основном подразумевал x86 и ARM). Есть другие, которые ведут себя по-разному, но они значительно реже. (Интересно: ARM когда-то была одной из архитектур, которые требовали согласованного доступа, но они добавили автоматическую обработку невыровненного доступа в более поздних версиях)
Жюль
2

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

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

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

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

Эрик Эйдт
источник