В каждом повторении цикла переменная j объявляется снова и снова. Затем, почему его адрес остается тем же?
- Разве этому нельзя дать некоторый случайный адрес каждый раз?
- Действительно ли этот компилятор зависим?
#include<stdio.h>
#include<malloc.h>
int main()
{
int i=3;
while (i--)
{
int j;
printf("%p\n", &j);
}
return 0;
}
Тестовый прогон:-
shadyabhi@shadyabhi-desktop:~/c$ gcc test.c
shadyabhi@shadyabhi-desktop:~/c$ ./a.out
0x7fffc0b8e138
0x7fffc0b8e138
0x7fffc0b8e138
shadyabhi@shadyabhi-desktop:~/c$
Причина, по которой адрес j
никогда не меняется, заключается в том, что компилятор выделяет память для j
в стеке когда функция вводится, а не когда j
входит в область видимости.
Как всегда, изучение некоторого ассемблерного кода может помочь объяснить концепцию. Возьмем следующую функцию: -
int foo(void)
{
int i=3;
i++;
{
int j=2;
i=j;
}
return i;
}
gcc преобразует ее в следующий ассемблерный код x86: -
foo:
pushl %ebp ; save stack base pointer
movl %esp, %ebp ; set base pointer to old top of stack
subl $8, %esp ; allocate memory for local variables
movl $3, -4(%ebp) ; initialize i
leal -4(%ebp), %eax ; move address of i into eax
incl (%eax) ; increment i by 1
movl $2, -8(%ebp) ; initialize j
movl -8(%ebp), %eax ; move j into accumulator
movl %eax, -4(%ebp) ; set i to j
movl -4(%ebp), %eax ; set the value of i as the function return value
leave ; restore stack pointers
ret ; return to caller
Давайте пройдемся по этому ассемблерному коду. Первая строка сохраняет текущий базовый указатель стека, чтобы его можно было восстановить при выходе из функции, вторая строка устанавливает текущую вершину стека как новый базовый указатель стека для этой функции.
Третья строка выделяет память в стеке для всех локальных переменных. Инструкция subl $ 8,% esp
вычитает 8 из текущей вершины указателя стека, регистра esp
. Стеки увеличиваются в памяти, поэтому эта строка кода фактически увеличивает память в стеке на 8 байт. У нас есть два целых числа в этой функции, i
и j
, каждое из которых требует 4 байта, поэтому оно выделяет 8 байтов.
Строка 4 инициализирует i
значением 3 путем прямой записи по адресу в стеке. Строки 5 и 6 затем загружают и увеличивают i
. Строка 7 инициализирует j
, записывая значение 2 в память, выделенную для j
в стеке. Обратите внимание, что когда j
вошел в область видимости в строке 7, ассемблерный код не настраивал стек для выделения памяти для него, о чем уже позаботились ранее.
Я уверен, что это очевидно, но причина, по которой компилятор выделяет память для всех локальных переменных в начале функции, заключается в том, что это более эффективно. Регулировка стека каждый раз, когда локальная переменная входит или выходит за пределы области видимости, приведет к множеству ненужных манипуляций с указателем стека без какой-либо выгоды.
Я уверен, что вы сами сможете решить, что делает остальная часть ассемблерного кода, если не разместите комментарий, и я проведу вас через него.
j и i выделяются в стеке, а не в куче или freestore (для чего потребуется malloc или new, соответственно). Стек помещает следующую переменную в детерминированное место (наверху стека), поэтому у нее всегда один и тот же адрес. Хотя, если вы работаете в оптимизированном режиме, переменная, вероятно, никогда не «освобождается», то есть размер стека не меняется на протяжении всей вашей программы, потому что это будет просто напрасно потраченными циклами.
На самом деле вы не используете malloc
, так в чем проблема?
Переменная является локальной для функции, и ее пространство зарезервировано в стеке во время компиляции ... так зачем ей перераспределять ее на каждой итерации? Просто потому, что он объявлен внутри цикла?
Как уже говорилось в других ответах, вы не выделяете здесь ничего, кроме как в стеке. Но даже если вы измените код следующим образом, это не обязательно изменит адрес размещения.
Это зависит от используемой библиотеки libc, обычно там находится malloc, но некоторые приложения (особенно firefox) переопределяют его для своего использования (проблемы фрагментации памяти и т. Д.).
#include<stdio.h>
#include<malloc.h>
int main()
{
int i=3;
while (i--)
{
int *j = (int *) malloc(sizeof(int));
printf("%p\n", j);
free (j);
}
return 0;
}
если вы закомментируете свободный (j), вы заметите, что адрес j действительно изменился. Но в зависимости от вашей libc адрес j всегда может измениться.
Это память на стеке. Она не выделяется из кучи. Стек не изменится в этом цикле.
Почему должно быть иначе? Компилятору требуется место в стеке для хранения int, и каждый раз, когда он проходит через цикл, доступно одно и то же пространство.
Кстати, вы вообще не используете malloc
. j
хранится в стеке.
j
размещается в стеке, поэтому во время одного вызова этой функции она всегда будет иметь один и тот же адрес.
Если бы вы вызывали main ()
из этого цикла, «внутренние» main
j
имели бы другой адрес, как и выше в стеке.
См. Аппаратный стек в Википедии для получения дополнительных сведений.
Вы не выполняете malloc-ing. Это адрес стека, поэтому он всегда один и тот же, потому что он всегда находится в одном и том же месте стека раз за разом.
Подсказка: как вы думаете, что это будет делать?
#include<stdio.h>
#include<malloc.h>
int main()
{
int i=3;
while (i--)
{
int j = 42;
printf("%p\n", &j);
}
return 0;
}
Теперь вы получите серию утечек выделения памяти, поскольку j не сохраняются для последующего освобождения. j не обязательно получит случайный адрес, но, вероятно, в виде последовательности по сравнению с предыдущими выделениями j.
Если вы освободите j в конце цикла, вы можете получить то же поведение, что и раньше, в зависимости от реализации malloc и free.
Изменить: вы можете захотеть перепроверить напечатанные значения с помощью этого кода.
Он объявлен внутри цикла, как вы говорите, но он выходит за пределы области видимости и «уничтожается» в конце каждой итерации (то есть не входит в область видимости и не существует, когда условие цикла проверяется). Поэтому совершенно законно повторное использование одного и того же места стека (на самом деле было бы ошибкой, если бы это было не так).