Почему локальные типы всегда получают четный адрес

Учитывая этот пример кода:

void func( char arg)
{
    char a[2];
    char b[3];
    char c[6];
    char d[5];
    char e[8];
    char f[13];
    std::cout << (int)&arg << std::endl;
    std::cout << (int)&a << std::endl;
    std::cout << (int)&b << std::endl;
    std::cout << (int)&c << std::endl;
    std::cout << (int)&d << std::endl;
    std::cout << (int)&e << std::endl;
    std::cout << (int)&f << std::endl;
}

Как получается, что при каждом вызове я получаю результат, подобный этому:

3734052
3734048
3734044
3734080
3734088
3734072
3734056

Когда каждый адрес является четным числом? И почему адреса находятся не в том же порядке, что и переменные в коде?

Ответ 1

И почему адреса не в том же порядке, что и переменные в коде?

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

Когда каждый адрес является четным числом?

Он помещает их вдоль границ слов. Это делает доступ к памяти быстрее, чем если бы они не были помещены на границы слов. Например, если a должно быть помещено в последний байт одного слова, а первый байт другого:

|             WORD 1                |                WORD 2             |
|--------|--------|--------|--------|--------|--------|--------|--------|
                           |  a[0]  |  a[1]  |

Тогда доступ к a[0], а затем a[1] потребует загрузки 2-х слов в кеш (при пропуске кеша для каждого). Поместив вдоль границы слова:

|             WORD 1                |
|--------|--------|--------|--------|
|  a[0]  |  a[1]  |

Ошибка кэширования a[0] приведет к одновременному загрузке как a[0], так и a[1] (уменьшение ненужной полосы пропускания памяти). Это использует принцип локальности. Хотя это, конечно, не требуется языком, это очень распространенная оптимизация, выполняемая компиляторами (если вы не используете директивы препроцессора, чтобы предотвратить ее).

В вашем примере (показано в их порядке):

3734044 b[0]
3734045 b[1]
3734046 b[2]
3734047 -----
3734048 a[0]
3734049 a[1]
3734050 -----
3734051 -----
3734052 arg
3734053 -----
3734054 -----
3734055 -----
3734056 f[0]
3734057 f[1]
3734058 f[2]
3734059 f[3]
3734060 f[4]
3734061 f[5]
3734062 f[6]
3734063 f[7]
3734064 f[8]
3734065 f[9]
3734066 f[10]
3734067 f[11]
3734068 f[12]
3734069 -----
3734070 -----
3734071 -----
3734072 e[0]
3734073 e[1]
3734074 e[2]
3734075 e[3]
3734076 e[4]
3734077 e[5]
3734078 e[6]
3734079 e[7]
3734080 c[0]
3734081 c[1]
3734082 c[2]
3734083 c[3]
3734084 c[4]
3734085 c[5]
3734086 -----
3734087 -----
3734088 d[0]
3734089 d[1]
3734090 d[2]
3734091 d[3]
3734092 d[4]

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

Ответ 2

В общем, это не имеет ничего общего с объемом переменных. Это связано с процессором.

Процессор, размер слова которого составляет 16 бит, любит извлекать переменные из четных адресов. Некоторые 16-разрядные процессоры получают только четные адреса. Таким образом, выборка из нечетного адреса потребует двух выборок, что удваивает количество обращений к памяти и замедляет работу программ.

Другие процессоры могут иметь более широкие требования. Многие 32-разрядные процессоры любят извлекать данные на 4-байтных границах. Опять же, очень похоже на пример 16-разрядного процессора выше.

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

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

Ответ 3

С каждым адресом является четное число?

Это может быть правдой на платформе/компиляторе, который вы используете. С++ не делает такой гарантии. Скорее всего, это сработает, но адреса указателей не определены как, и вы не можете иметь совместимый код, который рассчитывает, что указатели делятся на 2.

Ответ 4

Потому что так ваша реализация решила что-то сделать. Даже изменение некоторых параметров компилятора может изменить ситуацию. солнце CC и g++ создают нечетные адреса и более или менее в который вы ожидаете. Почему VС++ не делает, я могу только догадываться, но некоторые возможные причины могут быть:

  • Они добавляют дополнительные байты вокруг каждой переменной для проверки ошибок. В таких случаях может быть некоторое преимущество в дополнительные байты.

  • Они могут генерировать переменные в том порядке, в котором они происходят внутренняя хеш-таблица.

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