Почему последовательные переменные типа данных типа, расположенные со смещением 12 байтов в визуальной студии?

Чтобы прояснить вопрос, обратите внимание на фрагмент кода c/С++:

int a = 10, b = 20, c = 30, d = 40; //consecutive 4 int data values.

int* p = &d; //address of variable d.

Теперь, в visual studio (тестируется в 2013 году), если значение p == hex_value (которое можно просмотреть в окне памяти отладчика), то вы можете заметить, что адреса для других переменных a, b, c, и d каждый с разностью в 12 байт!

Итак, если p == hex_value, то следует:

&c == hex_value + 0xC (примечание hex C равно 12 в десятичной форме)

&b == &c + 0xC

&a == &b + 0xC 

Итак, почему есть смещение в 12 байт вместо 4 байтов - int всего 4 байта?

Теперь, если мы объявили массив:

int array[]  = {10,20,30,40};

Значения 10, 20, 30, 40 каждый расположены с разностью в 4 байта, как ожидалось!

Может кто-нибудь объяснить это поведение?

Ответы

Ответ 1

Стандартные состояния С++ в разделе 8.3.4 Массивы, которые: "Объект типа массива содержит смежно выделенный непустой набор из N подобъектов типа T".

Вот почему array[] будет множеством смежных int, и эта разница между одним элементом и следующим будет точно sizeof (int).

Для локальных/блочных переменных (автоматическое хранение) такая гарантия не предоставляется. Единственные утверждения приведены в разделе 1.7. Модель памяти С++: "Каждый байт имеет уникальный адрес". и 1.8. Объектная модель С++: "адрес этого объекта - это адрес первого байта, который он занимает. Два объекта (...) должны иметь разные адреса".

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

Теперь я играл с измененной версией вашего кода:

int a = 10, b = 20, c = 30, d = 40; //consecutive 4 int data values.
int* p = &d; //address of variable d.
int array[] = { 10, 20, 30, 40 };
char *pa = reinterpret_cast<char*>(&a), 
     *pb = reinterpret_cast<char*>(&b), 
     *pc = reinterpret_cast<char*>(&c), 
     *pd = reinterpret_cast<char*>(&d);
cout << "sizeof(int)=" << sizeof(int) << "\n &a=" << &a << \
  " +" << pa - pb << "char\n &b=" << &b << \
  " +" << pb - pc  << "char\n &c=" << &c << \
  " +" << pc - pd << "char\n &d=" << &d;
memset(&d, 0, (&a - &d)*sizeof(int));    
// ATTENTION:  undefined behaviour:  
// will trigger core dump on leaving 
// "Runtime check #2, stack arround the variable b was corrupted". 

При запуске этого кода я получаю:

debug                   release                comment on release

sizeof(int)=4           sizeof(int)=4       
 &a=0052F884 +12char     &a=009EF9AC +4char
 &b=0052F878 +12char     &b=009EF9A8 +-8char   // is before a 
 &c=0052F86C +12char     &c=009EF9B0 +12char   // is just after a !!
 &d=0052F860             &d=009EF9A4

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

Дополнительные пробелы в отладочной версии исходят из опции /RTCs. Я специально переписал переменные жестким memset(), который предполагает, что они смежны. При выходе из выполнения я сразу получаю сообщение: "Проверка выполнения # 2, стек arround, переменная b была повреждена", что наглядно демонстрирует цель этих дополнительных символов.
Если вы удалите этот параметр, вы получите с непрерывными переменными MSVC13, каждый из 4 байтов, как вы ожидали. Но больше не будет сообщений об ошибках в коррупции стека.