Простая маленькая программа из
учебника Лафоре, демонстрирующая работу указателя this (стр.517):
#include
class where
{
private:
char charray[10];
public:
void reveal()
{ std::cout << this << std::endl; }
};
int main()
{
where w1, w2, w3;
w1.reveal();
w2.reveal();
w3.reveal();
return 0;
}
Понятно, что при каждом новом запуске программы будут выведены отличающиеся адреса, но нас интересуют, собственно, не адреса, а расстояние между ними. Вот пример результата работы программы:
(тут надо бы отметить, что я пользуюсь операционной системой «Windows 7 Профессиональная» и компилятором из среды «Visual Studio Community 2017»)
003AF8AC
003AF8A0
003AF894
Как я понимаю, то, что объекты записываются в память в обратном порядке (адрес объекта w3 младше адреса объекта w2, а адрес объекта w2 младше адреса объекта w1) обусловлено тем, что они хранятся в
стеке, который работает по принципу
LIFO («последним вошёл - первым вышел»).
И вот тут начинается интересное. Так как объекты в данном случае записываются в памяти компьютера впритык друг к другу, рядышком, то, по идее, разница между выведенными на экран адресами объектов должна быть равна размеру этих объектов.
Размер каждого объекта, как видно из определения класса в программе, равен 10 байтам (так как размер типа char равен одному байту, а единственным полем класса является массив типа char из 10 элементов). Однако, разница между выведенными на экран адресами объектов равна 12 байтам (помним, что адреса здесь указаны в шестнадцатиричной системе счисления):
003AF8AC - 003AF8A0 = C16 = 1210
003AF8A0 - 003AF894 = C16 = 1210
Нижний индекс указывает на систему счисления.
Почему так происходит? Дело в том, что быстродействие процессора повышается, если хранить данные по адресам, кратным
машинному слову. Поэтому по умолчанию данные и сохраняются по таким адресам. То есть память используется неэкономно, зато повышается быстродействие. Подробнее об этом можно прочитать в википедии, но
на русском статья об этом представляет малюсенький огрызок, на английском -
статья гораздо полнее.
Так как в данном случае я создал 32-разрядное приложение, то размер машинного слова равен 4 байтам (32 бита). То есть процессору удобнее работать с данными, размер которых кратен 4 байтам: 4, 8, 12 и так далее. Размер нашего объекта 10 байт. Ближайший больший размер, удобный процессору, это 12 байт. Поэтому ради быстродействия для каждого нашего объекта размером в 10 байт выделяется область памяти размером в 12 байт, поэтому и разница между адресами соседних объектов получилась в 12 байт.
Чтобы это проверить, я уменьшил размер поля нашего класса (а, следовательно, и всех объектов этого класса) до 8 байт:
char charray[8];
Теперь разница между адресами, выведенными на экран, должна совпасть с размером объектов и должна быть равной 8 байтам. Результат работы измененной программы:
0035FC80
0035FC78
0035FC70
Так и есть:
0035FC80 - 0035FC78 = 816 = 810
0035FC78 - 0035FC70 = 816 = 810
Если же кровь из носу требуется записать объекты впритык рядышком в областях памяти размером, совпадающим с размером объектов (то есть без выравнивания по адресам, кратным машинному слову), то для этого можно дать указание компилятору с помощью спецификатора
alignas (в качестве аргумента спецификатору должно быть задано целое число, показатель кратности; для наших целей удобно задать единицу, так как любое число делится на единицу без остатка). Например, изменим программу так:
class alignas(1) where
{
private:
char charray[10];
public:
void reveal()
{ std::cout << this << std::endl; }
};
Результат работы измененной программы:
0042FDDE
0042FDE8
0042FDF2
Рассчитываем разницы между адресами, они (как и требовалось) равны 10 байтам:
0042FDF2 - 0042FDE8 = A16 = 1010
0042FDE8 - 0042FDDE = A16 = 1010
Еще изменился порядок записи объектов в память. Теперь они записываются по принципу
FIFO («первым вошёл - первым вышел»). Как я понимаю, теперь они сохраняются не в стеке, а в
куче.