Определение того, растет ли стек вниз, вычисляя разницу в адресах локальных переменных

#c #memory

#c #память

Вопрос:

Разве вычисление разницы в адресах переменных стека не было бы способом определить, растет ли стек вверх или вниз?

 int a;
int b;
printf ("Difference: %dn", (amp;b - amp;a); // if > 0, stack grows downwards
  

Комментарии:

1. «Вниз» не обязательно имеет смысл, потому что это полностью зависит от перспективы. Я не думаю, что хотя этот порядок переменных гарантирован, так что нет, я не думаю, что это сработает. Кажется , что это следует порядку объявления, когда типы данных имеют одинаковый размер, но я не думаю, что это обязательно должно быть так

2. примеры, которые я видел, либо используют рекурсивные функции, либо передают локальную переменную в функцию и сравнивают различия

3. @Carcigenicate не могли бы вы подробнее остановиться на части упорядочения переменных? int a выполняется первым и int b после, так что разве они не должны быть размещены соответствующим образом?

4. Только потому, что переменная объявлена раньше другой, она не обязательно помещается в том же порядке в памяти. Если компилятор считает, что он может лучше выровнять стек с другой конфигурацией, он может сохранить их в любом порядке, который он хочет. По моему опыту, когда данные имеют одинаковый размер, это, кажется, поддерживает порядок, поскольку любое из них привело бы к одинаковому выравниванию, но я не думаю, что это требуется.

5. @Carcigenicate so b может быть помещен выше a или наоборот в память стека и является неопределенным?

Ответ №1:

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

Переменные в стековом фрейме конкретной функции обычно будут вместе, однако, поэтому вы могли бы сделать это, сравнив адрес переменной в одной функции с адресом переменной в вызываемой функции. Например:

 void check_stack(void *ptr)
{
    int var;
    if (ptr) {
        if ((uintptr_t)amp;var > (uintptr_t)ptr) {
            printf("stack is growing upn");
        } else {
            printf("stack is growing downn");
        }
    } else {
        check_stack(amp;var);
    }
}
  

И вызовите функцию как check_stack(NULL);

Также обратите внимание, что указатели сначала преобразуются в uintptr_t . Это потому, что использование реляционных операторов над несвязанными указателями вызывает неопределенное поведение. A uintptr_t — это целочисленный тип, который гарантированно способен содержать преобразованное значение указателя, и именно эти значения сравниваются.

Комментарии:

1. разве var amp; ptr не одно и то же после первого рекурсивного вызова?

2. В рекурсивном вызове ptr будет адрес var из предыдущего вызова. Каждый вызов функции имеет свой собственный var .

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

4. @xyf Мы сравниваем переменные в разных фреймах стека. Это тот же стек, просто другая его часть.

5. @xyf Один и тот же стек, разные кадры стека. Поскольку стеки по замыслу могут добавляться только к началу, вы знаете, что при вызове функции ее фрейм стека будет размещен выше фрейма вызывающей функции.