То же пространство памяти, выделяемое снова и снова

В каждом повторении цикла переменная 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$
6
задан Abhijeet Rastogi 15 March 2010 в 15:09
поделиться

11 ответов

Причина, по которой адрес 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, ассемблерный код не настраивал стек для выделения памяти для него, о чем уже позаботились ранее.

Я уверен, что это очевидно, но причина, по которой компилятор выделяет память для всех локальных переменных в начале функции, заключается в том, что это более эффективно. Регулировка стека каждый раз, когда локальная переменная входит или выходит за пределы области видимости, приведет к множеству ненужных манипуляций с указателем стека без какой-либо выгоды.

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

6
ответ дан 8 December 2019 в 02:45
поделиться

j и i выделяются в стеке, а не в куче или freestore (для чего потребуется malloc или new, соответственно). Стек помещает следующую переменную в детерминированное место (наверху стека), поэтому у нее всегда один и тот же адрес. Хотя, если вы работаете в оптимизированном режиме, переменная, вероятно, никогда не «освобождается», то есть размер стека не меняется на протяжении всей вашей программы, потому что это будет просто напрасно потраченными циклами.

4
ответ дан 8 December 2019 в 02:45
поделиться

На самом деле вы не используете malloc , так в чем проблема?

Переменная является локальной для функции, и ее пространство зарезервировано в стеке во время компиляции ... так зачем ей перераспределять ее на каждой итерации? Просто потому, что он объявлен внутри цикла?

2
ответ дан 8 December 2019 в 02:45
поделиться

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

Это зависит от используемой библиотеки 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 всегда может измениться.

0
ответ дан 8 December 2019 в 02:45
поделиться

Это память на стеке. Она не выделяется из кучи. Стек не изменится в этом цикле.

12
ответ дан 8 December 2019 в 02:45
поделиться

Почему должно быть иначе? Компилятору требуется место в стеке для хранения int, и каждый раз, когда он проходит через цикл, доступно одно и то же пространство.

Кстати, вы вообще не используете malloc . j хранится в стеке.

6
ответ дан 8 December 2019 в 02:45
поделиться

j размещается в стеке, поэтому во время одного вызова этой функции она всегда будет иметь один и тот же адрес.

Если бы вы вызывали main () из этого цикла, «внутренние» main j имели бы другой адрес, как и выше в стеке.

См. Аппаратный стек в Википедии для получения дополнительных сведений.

2
ответ дан 8 December 2019 в 02:45
поделиться

Вы не выполняете malloc-ing. Это адрес стека, поэтому он всегда один и тот же, потому что он всегда находится в одном и том же месте стека раз за разом.

1
ответ дан 8 December 2019 в 02:45
поделиться

Подсказка: как вы думаете, что это будет делать?

#include<stdio.h>
#include<malloc.h>

int main()
{
    int i=3;
    while (i--)
    {
        int j = 42;
        printf("%p\n", &j);
    }
    return 0;
}
0
ответ дан 8 December 2019 в 02:45
поделиться

Теперь вы получите серию утечек выделения памяти, поскольку j не сохраняются для последующего освобождения. j не обязательно получит случайный адрес, но, вероятно, в виде последовательности по сравнению с предыдущими выделениями j.

Если вы освободите j в конце цикла, вы можете получить то же поведение, что и раньше, в зависимости от реализации malloc и free.

Изменить: вы можете захотеть перепроверить напечатанные значения с помощью этого кода.

-1
ответ дан 8 December 2019 в 02:45
поделиться

Он объявлен внутри цикла, как вы говорите, но он выходит за пределы области видимости и «уничтожается» в конце каждой итерации (то есть не входит в область видимости и не существует, когда условие цикла проверяется). Поэтому совершенно законно повторное использование одного и того же места стека (на самом деле было бы ошибкой, если бы это было не так).

1
ответ дан 8 December 2019 в 02:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: