Почему локальные типы всегда получают четный адрес
Учитывая этот пример кода:
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С++ не делает, я могу только догадываться, но
некоторые возможные причины могут быть:
-
Они добавляют дополнительные байты вокруг каждой переменной для проверки ошибок.
В таких случаях может быть некоторое преимущество в
дополнительные байты.
-
Они могут генерировать переменные в том порядке, в котором они происходят
внутренняя хеш-таблица.
Но это только догадки; есть, несомненно, другие причины
что также может вызвать поведение, которое вы видите.