Эти массивы имеют автоматическую продолжительность хранения, и концептуально новый экземпляр каждого массива создается каждый раз, когда выполняется оператор { … }
внутри цикла for
. Поскольку в различных итерациях вы запрашиваете разные размеры для массива a
, вполне разумно, чтобы реализация C поместила его в другое место в памяти, чтобы освободить место для его элементов. Ваша реализация C, кажется, использует блоки по 16 байт как единицу для того, сколько памяти она резервирует для массива или для его выравнивания. Это, вероятно, является следствием управления стеком, поскольку выравнивание или размер блока, скорее всего, не нужны для самого массива a
.
Вполне возможно, что на распределение a
, b
и c
влияет тот факт, что в абстрактном компьютере, указанном в стандарте C, время жизни b
начинается, как только выполняется начинается блок, но время жизни a
и c
начинается, когда выполнение («контроль») достигает операторов, которые их определяют. Это связано с тем, что в C 2018 6.2.4 говорится, что объекты с автоматической продолжительностью хранения, которые не имеют переменную длину, начинают жизнь после входа в связанный блок (пункт 6), а такие объекты, которые имеют переменную длину, начинают жизнь с объявления (пункт 7). Таким образом, когда код написан, b
сначала начинает жизнь, затем a
, затем c
.
Этот порядок размещения влияет на то, куда положено c
, а не на то, куда положено b
. Поскольку b
создается первым, он «раньше» в стеке (по более высокому адресу, что означает, что он получает адрес, еще не затронутый a
). Поскольку c
создается позже, он «позже» в стеке (по более низкому адресу, что означает, что он получает адрес, на который влияет размер a
). Этот порядок технически не требуется стандартом C, поскольку реализация C может располагать местоположения по своему усмотрению, если получены те же результаты, что определены стандартом C. Однако, похоже, ваша реализация точно следовала абстрактной компьютерной модели C, сначала создав b
, затем a
, затем c
.
Кроме того, правильным способом печати адресов объектов является использование спецификации формата %p
и преобразование адресов в void *
:
printf("%p %p %p\n", (void *) a, (void *) b, (void *) c);