Какой объем памяти занимают автопеременные в стеке

Я читал, что функции в C могут использовать локальные переменные на основе стека, и они выделяются просто путем уменьшения указателя стека на требуемое количество места. Это всегда делается 4-байтовыми фрагментами (если я не ошибаюсь). Но что, если запустить следующий код:

 void foo(void)
{
   char str[6];
   ......
}

Какой размер занимает переменная str? 6 байта или 6 × 4 байта в соответствии с четырехбайтовыми фрагментами.


person Tracy    schedule 24.03.2013    source источник


Ответы (5)


Правило четырехбайтового фрагмента просто означает, что указатель стека должен указывать на адрес, кратный четырем. В этом случае выделение 8 байт удовлетворяет этому правилу, и такой блок достаточно велик, чтобы вместить 6-символьный массив всего с 2 байтами заполнения.

person chepner    schedule 24.03.2013

Выравнивание данных — это требование ЦП, что означает, что величина выравнивания меняется от ЦП к другому, имейте это в виду.

Говоря о выравнивании данных в стеке, gcc, например, поддерживает выравнивание данных с помощью параметра -mpreferred-stack-boundary=n, где данные будут выравниваться по 2^n. По умолчанию значение n равно 4, что делает выравнивание стека 16-байтовым.

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

int main()
{
        char ar[6] = {1,2,3,4,5,6};
        int x = 10;
        int y = 12 + (int) ar[1] + x;
        return y;
}

Компиляция этого кода с помощью gcc на моем процессоре дает следующую сборку (публикация только инструкции по размещению стека):

subl    $32, %esp

Но почему 32? мы выделяем данные, которые умещаются точно в 16 байт. Ну, есть 8 байтов, которые gcc нужно сохранить для leave и ret, что делает общую необходимую память 24.
НО, требование выравнивания составляет 16 байтов, и поэтому gcc необходимо выделить пространство стека, чтобы оно было восполнено. кусков по 16 байт; преобразование этих 24 байтов в 32 решает проблему.
У вас будет достаточно места для ваших переменных, для ret и leave, и оно состоит из двух фрагментов по 16 байт.

person Fingolfin    schedule 24.03.2013

Правило выделения 4-байтовыми порциями действует не во всех случаях. Например, ARM eabi требует выравнивания 64-битных целых чисел и удваивания на 8-байтовых границах.

Обычно выделенное пространство соответствует правилам упаковки данных в структуры. Таким образом, char[6] фактически занимает 6 байтов (обычно), но заполнение данных (для следующего поля) может использовать еще несколько байтов.

Пример:

struct X
{
    char field1[6];
};

Таким образом, размер структуры X будет 8

structure Y
{
    char field1[2];
    double field2;
};

Структура Y обычно представляет собой что-то вроде 8, 12 или 16 байтов в зависимости от архитектуры.

Те же правила применяются к автоматическим переменным стека: обычно заполнение определяется не используемым типом, а следующим типом, который вы собираетесь использовать. И правила иногда немного расплывчаты.

person Valeri Atamaniouk    schedule 24.03.2013
comment
Можно заставить компилятор не заполнять ваши структуры данных, хотя, как я понимаю, это приведет к снижению производительности. - person ; 24.03.2013
comment
@dingrite Некоторые компиляторы позволяют определять определенное выравнивание данных в стеке. Но это исключение, так как правила определяются документацией платформы и делают части приложения несовместимыми со стандартной библиотекой и другим существующим кодом. - person Valeri Atamaniouk; 24.03.2013
comment
@dingrite Я забыл упомянуть упакованные структуры. Вы можете использовать их, но побочные эффекты и ограничения портативности могут перевесить пользу. - person Valeri Atamaniouk; 24.03.2013
comment
На самом деле я имел в виду приказание компилятору обрабатывать структуру (возможно, даже класс) без заполнения независимо от того, где она размещена. На самом деле я сам однажды использовал его, когда хотел выполнить «организованное» исправление asm памяти — мне нужна была структура без заполнения, поэтому я использовал #pragma pack(push, 1) и выталкивал в Visual Studio. - person ; 24.03.2013
comment
@dingrite Да, это сработает, но все равно выровняет саму структуру по границе. Таким образом, вы можете настроить выравнивание элементов структуры, но выравнивание стека останется предопределенным. - person Valeri Atamaniouk; 24.03.2013

Я думаю, вы путаетесь между размером данных и выравниванием данных. Общего правила нет, но на современных компьютерах ваша переменная будет храниться в 6 байтах. С другой стороны, следующий элемент не обязательно будет храниться в следующем байте. Это известно как заполнение структуры данных.

Архитектуры с выравниванием по словам, в которых каждая переменная должна начинаться с адреса, кратного размеру слова, становятся редкостью. В новых процессорах, таких как SPARC или x86, переменные самовыравниваются. Это означает, что они должны начинаться с адреса, кратного размеру его типа.

Следовательно, на неэкзотических компьютерах нет «правила четырех байтов». В вашем примере str будет храниться с 6 байтами. Например, если вы объявите переменную с выравниванием 8 байтов (например, double на x86), ваш компилятор вставит 2 байта заполнения.

Выравнивание фиксируется компилятором в соответствии с вашей архитектурой. Так что стандарт ничего не определяет по этому поводу. Дополнительную информацию можно найти в Википедии.

person md5    schedule 24.03.2013

Если у вас есть:

char str[6];
int a;
char b;
char c;

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

В моей системе компиляция приведенного выше и распечатка адресов переменных стека (начальные цифры удалены для краткости):

&str    -- 18
&a      -- 12
&b      -- 10
&c      -- 11

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

person teppic    schedule 24.03.2013