C для поиска направления роста стека

Как найти в C, идет ли стек в прямом или обратном направлении? Будет ли эта работа?

int j = 0; int k = 0; if (&k > &j) printf ("Stack is growing in forward direction"); else if (&k < &j) printf ("Stack is growing in reverse direction"); 

Чтобы быть надежным, нужно было бы найти разницу между двумя вызовами функций.

 void func(int *p) { int i; if (!p) func(&i); else if (p < &i) printf("Stack grows upward\n"); else printf("Stack grows downward\n"); } func(NULL); 

Обратите внимание, что это не даст вам ответа о C , а о вашем компиляторе.

Ты не можешь. В вашем коде (&k > &j) вызывает неопределенное поведение поведения. Сравнение указателей с реляционными операторами не определяется, если указатели не указывают на объекты в одном массиве (или один объект за пределами массива).

Наличие стека диктуется вашей реализацией. Неопределенное поведение не может предсказать детали реализации.

Стандарт ISO C не упоминает слово «стек» даже один раз. Стек может даже не существовать. Память, используемая функциями invocations для хранения локальных переменных, может быть даже не смежна.

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

Другими словами, ваша функция может работать, но это не совсем так. И если это не сработает, оно не сообщит об ошибке: вместо этого вы получите неверный результат и не сможете сказать. Стек и обработка соглашений о вызове – это единственные две вещи низкого уровня, которые C удается скрыть.

Мой ассемблер x86 является ржавым, но с моей головы эта функция сборки (синтаксис Intel) может дать правильные результаты. Его прототипом C будет int getGrowthDirection() ; он возвращает положительное число, если стек растет вперед и отрицательное число, если стек растет в обратном направлении.

 getGrowthDirection: mov ebx, esp push esp sub ebx, esp xor eax, eax sub eax, ebx pop esp ret 

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

Уже было указано, что среда выполнения C не обязательно использует стек (кадры активации функции могут быть выделены в куче). Итак, допустим, что у нас есть система, которая использует стек для автоматических переменных. Тогда мы могли бы определить направление стека, сравнив адреса переменных из двух разных кадров активации. Однако с этим подходом существуют две проблемы:

  1. Сравнение является незаконным. Если компилятор может сказать, что сравнение является незаконным или что сравнение, если оно является законным, должно иметь конкретный результат, то он может не генерировать код для выполнения сравнения. Например, если вы сопоставляете два указателя с типом T, а программа не содержит массивов типа T [] длиной больше 1, то компилятор может вывести, что указатели должны сравнивать равные.
  2. Как мы можем быть уверены, что переменные действительно находятся в разных активационных кадрах? Компилятор может преобразовывать некоторые автоматические переменные в статические переменные, и даже рекурсивные функции могут быть встроены (GCC строит простую рекурсивную факториальную функцию).

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

Размышляя обо всем этом, я сначала отвлекся на мысль о преобразовании указателей в целые числа (C99’s uintptr_t). Думаю, это красная сельдь. Во-первых, сравнение целых чисел может не дать того же результата, что и сравнение исходных указателей, поэтому вам все равно придется их перевести обратно. Во-вторых, мы не пытаемся скрывать от компилятора, что мы сравниваем указатели; мы только пытаемся скрыть от компилятора, какие указатели мы сравниваем.

Я нашел, что это помогло сначала рассмотреть вторую проблему: как мы можем гарантировать, что у нас есть указатели на переменные в разных активационных кадрах?

Давайте откажемся от идеи поместить одну функцию в отдельную библиотеку или динамически загруженный модуль: это будет не переносимым, и если мы будем не переносимы, тогда мы можем также распечатать указатели с printf («% p \ n “, p) и сравнить их с утилитами shell. Помимо того, что он не переносится, это тоже не забавно.

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

Есть различные способы, которыми мы могли бы сделать исполнение предсказуемым для нас, но непонятно компилятору. Мы могли бы использовать сложную математику или генератор псевдослучайных чисел. Однако, вероятно, это достаточно хорошо, чтобы заставить его потенциально зависеть от аргументов командной строки, причем поведение, которое мы хотим, является поведением по умолчанию без аргументов (надеясь, что ни один компилятор реального мира не оптимизирует программу, делая символическую интерпретацию с предположением что он будет выполнен без аргументов). Таким образом, мы могли бы выполнить последовательность операций, указанных явно в argv [1], и программа будет своего рода мини-интерпретатором. При таком подходе я думаю, что могу ответить на исходный вопрос следующей программой, которая пытается быть переносимой без использования файлов заголовков или библиотечных функций:

 // Program to determine stack direction by Edmund Grimley Evans void *mem[99]; void **p = mem; char *pc; void run(void) { void *a[2]; for (;;) { switch (*pc++) { case '+': ++p; break; case '-': --p; break; case 't': { void *t = p[0]; p[0] = p[1]; p[1] = t; } break; case 'a': p[0] = &a[0]; p[1] = &a[1]; break; case 'p': *p = p; break; case 'l': *p = *(void **)*p; break; case 's': *(void **)p[0] = p[1]; break; case '<': *p = (p[0] < p[1]) ? p : 0; break; case 'c': run(); break; case 'r': return; } } } int main(int argc, char *argv[]) { pc = argc == 2 ? argv[1] : "ac+ac+ac- 

Вот более длинная версия с комментариями и выводами трассировки, чтобы объяснить, как это работает:

 // Program to determine stack direction by Edmund Grimley Evans #include  #include  void *mem[99]; // memory void **p = mem; // pointer to memory char *pc; // program counter int depth = 0; // number of nested calls, only for debug // An interpreter for a strange programming language. // There are 10 instructions in the instruction set: "+-tapls p && !*e) --e; printf(" %d:", depth); for (t = mem; t <= e; t++) printf(t == p ? " [%p]" : " %p", *t); printf("\n%c\n", *pc); } switch (*pc++) { // increment memory pointer: case '+': ++p; break; // decrement memory pointer: case '-': --p; break; // swap contents of adjacent memory cells: case 't': { void *t = p[0]; p[0] = p[1]; p[1] = t; } break; // save addresses of local array in memory: case 'a': p[0] = &a[0]; p[1] = &a[1]; break; // save address of memory itself in memory: case 'p': *p = p; break; // load: case 'l': *p = *(void **)*p; break; // store: case 's': *(void **)p[0] = p[1]; break; // compare two pointers: case '<': *p = (p[0] < p[1]) ? p : 0; break; // recursive call to interpreter: case 'c': ++depth; run(); --depth; break; // return: case 'r': return; default: printf(" Error!\n"); exit(1); } } } int main(int argc, char *argv[]) { // The default program does three recursive calls and compares // addresses from the last two frames: pc = argc == 2 ? argv[1] : "ac+ac+ac- 

Заметьте, что я почти не тестировал эту программу!

Я был первоначально привлечен к этой проблеме неудачным тестом autoconf в пакете librep Debian. Тем не менее, я смущаюсь рекомендовать еще непроверенную программу, подобную этой, для использования в тесте autoconf. На практике я бы предположил, что безопаснее предположить, что все стеки нисходят, если у нас нет признанного исключения, такого как архитектура «hppa» Debian.

В процессе Linux (или другой операционной системы) при вызове подпрограммы память для локальных переменных поступает из области стека процесса. Любая динамически распределенная память (с использованием malloc, new и т. Д.) Происходит из области кучи процесса. Во время рекурсии локальная память выделяется из области стека во время вызова функции и очищается при выполнении функции.

Память изображается с наименьшим адресом, находящимся внизу, а верхний – сверху. Ниже приведены шаги по поиску направления роста стека в рекурсии с использованием быстрого кода C.

 #include  void test_stack_growth_direction(recursion_depth) { int local_int1; printf("%p\n", &local_int1); if (recursion_depth < 10) { test_stack_growth_direction(recursion_depth + 1); } } main () { test_stack_growth_direction(0); } 

вне на MAC

 0x7fff6e9e19ac 0x7fff6f9e89a8 0x7fff6f9e8988 0x7fff6f9e8968 0x7fff6f9e8948 0x7fff6f9e8928 0x7fff6f9e8908 0x7fff6f9e88e8 0x7fff6f9e88c8 0x7fff6f9e88a8 0x7fff6f9e8888 

выход на ubuntu

 0x7ffffeec790c 0x7ffffeec78dc 0x7ffffeec78ac 0x7ffffeec787c 0x7ffffeec784c 0x7ffffeec781c 0x7ffffeec77ec 0x7ffffeec77bc 0x7ffffeec778c 0x7ffffeec775c 0x7ffffeec772c 

Стек растет на этих конкретных настройках, поскольку адреса памяти уменьшаются. Это зависит от архитектуры системы и может иметь другое поведение для других архитектур. 0x7fff6f9e8868