Базовый адрес массива изменяется при объявлении внутри цикла - PullRequest
5 голосов
/ 21 июня 2019

Я объявил массив внутри цикла и попытался напечатать его базовый адрес.

#include<stdio.h>

int main(){
  int n=16;
  for(int i=1;i<=n;i++){
    int a[i];
    int b[16];
    int c[n];
    printf("%p %p %p\n",(void *)a,(void *)b,(void *)c);
  }
  return 0;
}

Вывод идет следующим образом:

0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191740 0x7fffe6191770 0x7fffe6191700
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191730 0x7fffe6191770 0x7fffe61916f0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191720 0x7fffe6191770 0x7fffe61916e0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0
0x7fffe6191710 0x7fffe6191770 0x7fffe61916d0

Почему базовый адрес массива меняется каждый раз? Выделена ли память для каждой итерации? Если так, то почему адрес не меняется в течение 4 итераций?

Пожалуйста, объясните различия между a, b и c в объявлениях, распределении памяти и базовых адресах.

Ответы [ 3 ]

6 голосов
/ 21 июня 2019

Эти массивы имеют автоматическую продолжительность хранения, и концептуально новый экземпляр каждого массива создается каждый раз, когда выполняется оператор { … } внутри цикла 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);
4 голосов
/ 21 июня 2019

Размер b одинаков на каждой итерации цикла. Компилятор находит его один раз, и он остается на месте.

И a, и c являются массивами технической длины. Размер c не изменяется, но кажется, что он расположен по более низкому адресу, чем a.

Ваш массив a увеличивается, поэтому, чтобы сохранить массив от массива b, начальный адрес должен быть ниже в стеке. И, поскольку c находится ниже a в стеке, он также перемещается по мере роста a. Компилятор, по-видимому, выделяет стек в виде квантов по 16 байтов. Когда массив сталкивается с другими переменными, его начало перемещается вниз по стеку еще одним квантовом из 16 байтов.

Умный компилятор может определить, что c - это фиксированный размер во время цикла, и изменить его порядок так, чтобы он отображался выше a в стеке. Тогда только a изменит адрес. Похоже, ваш компилятор этого не делает.

Макет стека выглядит следующим образом:

1-ые 4 цикла:

b 0x…901d0 
a 0x…901a0   gap to b is 0x30
c 0x…90160   gap to a is 0x40

2-й цикл:

b 0x…901d0
a 0x…90190  gap to b is 0x40
c 0x…90150  gap to a is 0x40

3-ий 4 цикла:

b 0x…901d0
a 0x…90180   gap to b is 0x50
c 0x…90140   gap to a is 0x40

Это поведение полностью на усмотрение компилятора. У меня нет хорошего объяснения, почему разрыв между a и b такой большой, когда в a есть 1..4 записей Он может размещать переменные в разных местах каждый раз. Технически, массивы выходят из области видимости, когда цикл заканчивается; переменные переопределяются в каждом цикле цикла. Ни один из них не инициализирован. Два из них не могут быть инициализированы; Вы не можете предоставить инициализатор для VLA.

0 голосов
/ 21 июня 2019

Автоматические переменные размещаются в стековой памяти и существуют только в используемом блоке. Как было сказано @Clonk, он зависит от реализации, поэтому он даст разные результаты в разных реализациях, но они действительны, так как размер массивов заранее неизвестно, они окажутся там, где есть непрерывный блок памяти.

См. https://en.wikipedia.org/wiki/Variable-length_array

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...