Различается ли выравнивание памяти для разных типов данных - PullRequest
4 голосов
/ 14 января 2010

Разные типы данных в C, такие как char, short, int, long, float, double, имеют разные границы выравнивания памяти? В 32-разрядной адресуемой в байтах адресуемой операционной системе чем отличается доступ к char или short от доступа к int или float? В обоих случаях процессор читает полное 32-битное слово? Что происходит, когда int не на границе? Как он может читать char по любому адресу памяти?

Ответы [ 8 ]

6 голосов
/ 14 января 2010

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

#include <iostream>

int main()
{
    using namespace std;

    char c;
    short s;
    int i;

    cout << "sizeof(char): " << sizeof(char) << endl;
    cout << "sizeof(short): " << sizeof(short) << endl;
    cout << "sizeof(int): " << sizeof(int) << endl;

    cout << "short is " << (int)&s - (int)&c << " bytes away from a char" << endl;
    cout << "int is " << (int)&i - (int)&s << " bytes away from a short" << endl;
}

Выход:

sizeof(char): 1
sizeof(short): 2
sizeof(int): 4
short is 1 bytes away from a char
int is 4 bytes away from a short

Как видите, он добавил некоторые отступы между int и short. Это не беспокоило коротких. В других случаях обратное может быть правдой. Правила оптимизации сложны.

И, предупреждение: компилятор умнее вас. Не играйте с отступами и выравниванием, если у вас нет действительно веской причины. Просто верьте, что то, что делает компилятор, является правильным.

5 голосов
/ 14 января 2010

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

Однако компиляторы, такие как gcc, предоставляют директивы, специфичные для компилятора , которые можно использовать для «упаковки» смежных переменных различных типов (и, следовательно, размеров), чтобы сэкономить память за счет производительности (но это то, что вы решаете, используя директиву по упаковке.) Смотрите этот вопрос.

ЦП может читать полное 32-битное слово (и, возможно, больше, чтобы получить всю строку кэша) при чтении символа / короткого символа.

4 голосов
/ 14 января 2010

Много вопросов ...

Имеют ли различные типы данных в C, такие как char, short, int, long, float, double, разные границы выравнивания памяти?

Да. Точные границы выравнивания зависят от компилятора, а некоторые позволяют изменить способ упаковки struct s. (Лучше всего вставлять поля заполнения, чтобы не допустить возникновения проблемы.)

В 32-разрядной адресуемой байтовой адресуемой операционной системе чем доступ к символу или короткому замыканию отличается от доступа к int или float?

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

В обоих случаях процессор читает полное 32-битное слово?

Не обязательно. При включении байтов вам не нужно читать полное 32-битное слово. Включение байтов также позволяет записывать отдельные байты в> 8-битной архитектуре без выполнения операций чтения-изменения-записи.

Что происходит, когда int не находится на границе?

Некоторые архитектуры (например, x86, IIRC) будут выполнять несколько обращений и объединять части для вас. Другие (например, PowerPC) генерируют ошибку шины или подобное исключение.

Как можно читать символ на любом адресе памяти?

Потому что адреса в вашей архитектуре квантованы байтами. Это не относится ко всем архитектурам. DSP известны наличием выровненных по слову указателей, то есть указатель является адресом слова, а не байтовым адресом. (Мне пришлось написать драйвер последовательного порта для одного из них. sizeof(char) == sizeof(short) == 1 == 16 бит. Поэтому вы должны выбирать между простым кодом, который тратит половину оперативной памяти, и большим количеством байтового пакета / кода распаковки.)

2 голосов
/ 15 января 2010

Возможно, вы захотите изучить выходные данные этой программы - скомпилированные как для 32-разрядных, так и для 64-разрядных на Intel Mac с MacOS X 10.6.2.

/*
@(#)File:           $RCSfile: typesize.c,v $
@(#)Version:        $Revision: 1.7 $
@(#)Last changed:   $Date: 2008/12/21 18:25:17 $
@(#)Purpose:        Structure sizes/alignments
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990,1997,2004,2007-08
@(#)Product:        :PRODUCT:
*/

#include <stdio.h>
#include <time.h>
#include <stddef.h>
#if __STDC_VERSION__ >= 199901L
#include <inttypes.h>
#endif /* __STDC_VERSION__ */

#define SPRINT(x)   printf("%2u = sizeof(" #x ")\n", (unsigned int)sizeof(x))

int main(void)
{
    /* Basic Types */
    SPRINT(char);
    SPRINT(unsigned char);
    SPRINT(short);
    SPRINT(unsigned short);
    SPRINT(int);
    SPRINT(unsigned int);
    SPRINT(long);
    SPRINT(unsigned long);
#if __STDC_VERSION__ >= 199901L
    SPRINT(long long);
    SPRINT(unsigned long long);
    SPRINT(uintmax_t);
#endif /* __STDC_VERSION__ */
    SPRINT(float);
    SPRINT(double);
    SPRINT(long double);
    SPRINT(size_t);
    SPRINT(ptrdiff_t);
    SPRINT(time_t);

    /* Pointers */
    SPRINT(void *);
    SPRINT(char *);
    SPRINT(short *);
    SPRINT(int *);
    SPRINT(long *);
    SPRINT(float *);
    SPRINT(double *);

    /* Pointers to functions */
    SPRINT(int (*)(void));
    SPRINT(double (*)(void));
    SPRINT(char *(*)(void));

    /* Structures */
    SPRINT(struct { char a; });
    SPRINT(struct { short a; });
    SPRINT(struct { int a; });
    SPRINT(struct { long a; });
    SPRINT(struct { float a; });
    SPRINT(struct { double a; });
    SPRINT(struct { char a; double b; });
    SPRINT(struct { short a; double b; });
    SPRINT(struct { long a; double b; });
    SPRINT(struct { char a; char b; short c; });
    SPRINT(struct { char a; char b; long c; });
    SPRINT(struct { short a; short b; });
    SPRINT(struct { char a[3]; char b[3]; });
    SPRINT(struct { char a[3]; char b[3]; short c; });
    SPRINT(struct { long double a; });
    SPRINT(struct { char a; long double b; });
#if __STDC_VERSION__ >= 199901L
    SPRINT(struct { char a; long long b; });
#endif /* __STDC_VERSION__ */

    return(0);
}

Вывод из 64-битной компиляции:

 1 = sizeof(char)
 1 = sizeof(unsigned char)
 2 = sizeof(short)
 2 = sizeof(unsigned short)
 4 = sizeof(int)
 4 = sizeof(unsigned int)
 8 = sizeof(long)
 8 = sizeof(unsigned long)
 8 = sizeof(long long)
 8 = sizeof(unsigned long long)
 8 = sizeof(uintmax_t)
 4 = sizeof(float)
 8 = sizeof(double)
16 = sizeof(long double)
 8 = sizeof(size_t)
 8 = sizeof(ptrdiff_t)
 8 = sizeof(time_t)
 8 = sizeof(void *)
 8 = sizeof(char *)
 8 = sizeof(short *)
 8 = sizeof(int *)
 8 = sizeof(long *)
 8 = sizeof(float *)
 8 = sizeof(double *)
 8 = sizeof(int (*)(void))
 8 = sizeof(double (*)(void))
 8 = sizeof(char *(*)(void))
 1 = sizeof(struct { char a; })
 2 = sizeof(struct { short a; })
 4 = sizeof(struct { int a; })
 8 = sizeof(struct { long a; })
 4 = sizeof(struct { float a; })
 8 = sizeof(struct { double a; })
16 = sizeof(struct { char a; double b; })
16 = sizeof(struct { short a; double b; })
16 = sizeof(struct { long a; double b; })
 4 = sizeof(struct { char a; char b; short c; })
16 = sizeof(struct { char a; char b; long c; })
 4 = sizeof(struct { short a; short b; })
 6 = sizeof(struct { char a[3]; char b[3]; })
 8 = sizeof(struct { char a[3]; char b[3]; short c; })
16 = sizeof(struct { long double a; })
32 = sizeof(struct { char a; long double b; })
16 = sizeof(struct { char a; long long b; })

Вывод из 32-битной компиляции:

 1 = sizeof(char)
 1 = sizeof(unsigned char)
 2 = sizeof(short)
 2 = sizeof(unsigned short)
 4 = sizeof(int)
 4 = sizeof(unsigned int)
 4 = sizeof(long)
 4 = sizeof(unsigned long)
 8 = sizeof(long long)
 8 = sizeof(unsigned long long)
 8 = sizeof(uintmax_t)
 4 = sizeof(float)
 8 = sizeof(double)
16 = sizeof(long double)
 4 = sizeof(size_t)
 4 = sizeof(ptrdiff_t)
 4 = sizeof(time_t)
 4 = sizeof(void *)
 4 = sizeof(char *)
 4 = sizeof(short *)
 4 = sizeof(int *)
 4 = sizeof(long *)
 4 = sizeof(float *)
 4 = sizeof(double *)
 4 = sizeof(int (*)(void))
 4 = sizeof(double (*)(void))
 4 = sizeof(char *(*)(void))
 1 = sizeof(struct { char a; })
 2 = sizeof(struct { short a; })
 4 = sizeof(struct { int a; })
 4 = sizeof(struct { long a; })
 4 = sizeof(struct { float a; })
 8 = sizeof(struct { double a; })
12 = sizeof(struct { char a; double b; })
12 = sizeof(struct { short a; double b; })
12 = sizeof(struct { long a; double b; })
 4 = sizeof(struct { char a; char b; short c; })
 8 = sizeof(struct { char a; char b; long c; })
 4 = sizeof(struct { short a; short b; })
 6 = sizeof(struct { char a[3]; char b[3]; })
 8 = sizeof(struct { char a[3]; char b[3]; short c; })
16 = sizeof(struct { long double a; })
32 = sizeof(struct { char a; long double b; })
12 = sizeof(struct { char a; long long b; })

Вы можете играть во все виды игр со структурами. Ключевым моментом является то, что требования к выравниванию для разных типов различаются. В зависимости от платформы у вас могут быть более или менее строгие требования. SPARC суетливый; Intel, как правило, выполняет больше работы, если вы делаете неправильный доступ (поэтому он медленный, но работает); старые микросхемы DEC Alpha (и я думаю, что микросхемы MIPS RISC) можно переключать на другое поведение, либо более эффективно, всегда требуя согласованного доступа, либо менее эффективно имитируя то, что делают чипы Intel.

2 голосов
/ 15 января 2010

Краткий ответ: это зависит от вашего компилятора и архитектуры. Большинство компиляторов имеют какую-то опцию командной строки или #pragma, которую вы можете использовать, чтобы вручную указать или изменить выравнивание переменных.

Однажды я использовал что-то подобное для исследования выравнивания данных разных типов:

union {
  struct {
    char one;
    char two;
    char three;
    char four;
  } chars;
  struct {
    short one;
    short two;
    short three;
    short four;
  } shorts;
  struct {
    int one;
    int two;
    int three;
    int four;
  } ints;
  struct {
    double one;
    double two;
    double three;
    double four;
  } doubles;
  /* etc, etc */
} many_types;

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

1 голос
/ 14 января 2010

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

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

Если данные не выровнены, поведение зависит от платформы. На некоторых аппаратных платформах попытка получить доступ к невыровненным данным приведет к сбою (например, на машинах Sun). В то время как на другой аппаратной платформе это может привести к небольшой потере эффективности и / или атомарности доступа, без каких-либо других негативных последствий (например, для машин Intel x86).

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

1 голос
/ 14 января 2010

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

Например, в x86 доступ к памяти через неправильно выровненный указатель может привести к повышению значения SIGBUS, если установлены оба параметра EFLAGS.AC и CR0.AM (см. этот ответ ).

0 голосов
/ 14 января 2010

Да. На типичном, но не универсальном примере:

1 символ
2 коротких
4 int
4 поплавка
8 двухместных

То, что делает процессор, это бизнес процессора и компилятора. На ограничивающих процессорах это учитывают компиляторы. На чипе RISC-y ЦПУ, возможно, придется загрузить 32 бита и сместить его, чтобы получить символ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...