Я пишу такой код

int a = 0;
int b = 0;
int *m = (int *)malloc(2* sizeof(int));

printf("%x, %x\n", &a, &b);
printf("%x, %x", &m[0], &m[1]);

И получить результат:

46372e18, 46372e1c
d062ec20, d062ec24

Разве этот стек не растет и не накапливается?

1
jfxu 4 Сен 2017 в 16:19

5 ответов

Лучший ответ

1 2

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

static ptrdiff_t
stack_probe(uintptr_t stack_addr_from_main)
{
    int var;
    uintptr_t stack_addr_from_me = (uintptr_t)&var;

    return ((intptr_t) stack_addr_from_me) - 
           ((intptr_t) stack_addr_from_main);
}

int
main(void)
{
    int var;
    uintptr_t stack_addr_from_main = (uintptr_t)&var;
    ptrdiff_t stack_delta = stack_probe(stack_addr_from_main);
    printf("Stack offset from one function call = %td\n", stack_delta);
    return 0;
}

Вы должны сделать это таким образом, потому что большинство компиляторов выделяют все пространство стека для вызова функции все сразу , после входа, в так называемом «стековом фрейме», и организуют пространство внутри, как они видят поместиться. Поэтому сравнение адреса двух переменных, локальных для одной и той же функции, не даст вам ничего полезного. Вы также должны позаботиться о том, чтобы скомпилировать эту программу с отключенным «inlining»; если компилятору разрешено сливать stack_probe в main, то все это снова будет один кадр стека, и результаты будут бессмысленными. (Некоторые компиляторы позволяют управлять встраиванием для каждой функции отдельно, но, насколько я знаю, стандартного способа сделать это не существует.)

Число, напечатанное этой программой, «не указано» стандартом C 3 (это означает, что «оно напечатает некоторый номер», но стандарт не требует, чтобы оно было каким-либо конкретный номер "). Тем не менее, почти на всех компьютерах, которые вы можете получить сегодня, он напечатает отрицательное число, а это означает, что стек растет вниз. Если вам удастся запустить его на компьютере PA-RISC с HP-UX (к сожалению, он может даже не скомпилироваться; я не помню, имел ли когда-нибудь HP-UX библиотеку, совместимую с C99), он напечатает положительное число, и это означает, что стек растет вверх.

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

Кстати, «куча» не обязательно увеличивается или вниз. Последовательные вызовы malloc всегда возвращают указатели, не имеющие значимых связей друг с другом.


1

2 Забавный факт: слово "стек" не встречается в стандарте Си нигде . Существует требование поддерживать рекурсивные вызовы функций, но как управление реализуется, полностью зависит от реализации.

3 Кроме того, определяется, будет ли эта программа компилироваться реализацией, потому что реализация не обязана предоставлять intptr_t и uintptr_t. Но если бы я не использовал эти типы, программа имела бы поведение undefined («ей разрешено делать все что угодно, включая сбой и удаление всего кода»), потому что вы можете только взять разницу двух указателей, когда они указывают на один и тот же массив, что они не делают.

8
zwol 4 Сен 2017 в 13:55

Все это не указано в стандарте. Стандарт даже не упоминает стек и кучу. Обе эти детали реализации не требуются стандартом.

Кроме того, у вас есть только один динамический объект (объект malloc), и он будет следовать за обычным расположением для массива. Таким образом, вы не можете ничего сказать о том, как растет куча в вашей системе. Если вы хотите посмотреть, что делает ваша система, вам понадобится как минимум два объекта malloc.

Для печати указателей используйте:

printf("%p, %p\n", (void*)&a, (void*)&b);
3
4386427 4 Сен 2017 в 13:29

Из-за арифметики указателей:

printf("%x, %x", &m[0], &m[1]);

(обратите внимание, что для печати значений указателя требуется формат %p, другие форматы могут «работать», но они также могут сломаться)

Адрес m[1] является адресом m[0] плюс sizeof(int), который не зависит от того, где выделено m (глобальное или автоматическое)

И здесь:

int a = 0;
int b = 0;

В противоположность элементам структуры, компилятор может находить автоматические переменные везде, где он выбирает относительно друг друга. Он может поменять их местами, сгруппировать с более низким ограничением выравнивания и т. Д. Итак, вы смотрите здесь детали реализации.

1
Jean-François Fabre 5 Сен 2017 в 07:16

Да, стек растет практически на всех современных системах. Тем не менее, 1. это детали реализации, и 2. это в терминах стековых фреймов , а не отдельных переменных.

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

Однако, если вы запустили этот код:

void foo() {
    int b;
    printf("%p\n", (void*)&b);
}

int main() {
    int a;
    printf("%p\n", (void*)&a);
    foo();
}

Вы увидите, что адрес b меньше адреса a: когда вызывается foo(), он создает новый кадр стека, и b будет внутри этого кадр стека, который будет ниже кадра стека, выделенного main().

Для кучи нет понятия направления вообще. Конечно, большинство распределителей будут демонстрировать некоторые паттерны в том, как они распределяют свою память, но, поскольку все они повторно используют память, которая была free() d, они все в какой-то момент ломают свои паттерны.

0
cmaster - reinstate monica 5 Сен 2017 в 08:44

Как указывалось многими, C stranded ничего не указывает по этому поводу.

Однако я хочу коснуться другого аспекта, теста, который вы делаете, недостаточно, чтобы определить, растет ли куча слов или слов.

Обновите код, как показано ниже, и проверьте результаты;

#include <stdio.h>
#include <stdlib.h>


void test()
{
    int a = 0;
    int b = 0;
    printf("4:%p, %p\n", (void *)&a,(void *)&b);
}

void  main() {
    int a = 0;
    int b = 0;
    int *m = malloc(2* sizeof(int));
    int *n = malloc(2* sizeof(int));

    printf("1:%p, %p\n",(void *)&a,(void *)&b);
    printf("2:%p, %p\n",(void *)&m[0],(void *)&m[1]);
    printf("3:%p, %p\n",(void *)&n[0],(void *)&n[1]);

    test();
}

В моей системе я вижу, что адрес кучи «увеличивается», а адрес стека «уменьшается»

В вашем коде нет вызова функции, поэтому стековый фрейм только один.

0
Vagish 5 Сен 2017 в 07:58