C程序找到堆栈增长的方向

时间:2011-06-21 01:10:56

标签: c stack

如何在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");

5 个答案:

答案 0 :(得分:13)

为了可靠,人们必须找到两个函数调用之间的区别。

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 的答案,而是关于编译器的答案。

答案 1 :(得分:6)

你做不到。在您的代码中,(&k > &j)调用未定义的行为行为。除非指针指向同一数组中的对象(或超出数组末尾的一个对象),否则不会定义与关系运算符的指针比较。

堆栈是否存在取决于您的实现。未定义的行为无法预测实现细节。

ISO C标准甚至没有提到“堆叠”一词。堆栈可能甚至不存在。函数调用用于保存局部变量的内存可能甚至不是连续的。

答案 2 :(得分:3)

这不是一个容易在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

请注意,此功能是无用的,因为程序集要求您了解您正在定位的平台,如果您知道要定位的平台,那么您应该知道堆栈增长方向。

答案 3 :(得分:3)

已经指出C执行环境不一定使用堆栈(可以在堆上分配功能激活帧)。因此,让我们假设我们有一个系统确实使用堆栈来自动变量。然后我们可以通过比较来自两个不同激活帧的变量的地址来确定堆栈方向。但是,这种方法存在两个问题:

  1. 比较是非法的。如果编译器可以判断比较是非法的,或者比较(如果它是合法的)必须具有特定结果,那么它可能不会生成执行比较的代码。例如,如果比较两个指向类型T的指针并且程序不包含长度大于1的T []类型的数组,则编译器可能会推断出指针必须比较相等。
  2. 我们怎样才能确定变量确实在不同的激活帧中?编译器可以将一些自动变量转换为静态变量,甚至可以内联递归函数(GCC内联一个简单的递归因子函数)。
  3. 如果我们有一个符号执行环境可以在运行时检测非法指针比较,那么第一个问题是不可解决的。因此,让我们假设我们有一个传统的优化编译器,它表示具有裸机器地址的指针(当它们无法被优化时)。

    考虑到这一切,我最初被将指针转换为整数的想法分散了注意力(C99&#39; s uintptr_t)。但我认为这是一个红鲱鱼。首先,比较整数可能不会给出与比较原始指针相同的结果,因此无论如何都必须将它们转换回来。其次,我们不是试图从编译器中隐藏我们正在比较指针;我们只是试图隐藏编译器我们正在比较的指针。

    我发现首先考虑第二个问题是有帮助的:我们怎样才能确保我们在不同的激活帧中有变量指针?

    让我们拒绝将一个函数放在一个单独的库或动态加载的模块中的想法:这将是不可移植的,如果我们将是不可移植的,那么我们也可以用printf打印出指针( &#34;%p \ n&#34;,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-<rrrr";
        run();
        return !!*p;
    }
    

    这是一个带有注释和跟踪输出的较长版本,用于解释其工作原理:

    // Program to determine stack direction by Edmund Grimley Evans
    
    #include <stdio.h>
    #include <stdlib.h>
    
    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<cr".
    // Not all are used in the default program that determines the
    // stack direction, but the others are required to prevent a clever
    // compiler from deducing that pointers will never be dereferenced,
    // or that a local variable will never be written to, for example.
    void run(void)
    {
        // The local variable is an array so that pointer comparison
        // might make sense:
        void *a[2];
    
        for (;;) {
            {
                // Print contents of memory:
                void **t, **e = mem + sizeof(mem) / sizeof(*mem) - 1;
                while (e > 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-<rrrr";
        run();
        printf("  Exit with %p (%d)\n", *p, !!*p);
        return !!*p;
    }
    

    请注意,我几乎没有测试过这个程序!

    我最初是因为在Debian&#34; librep&#34;中失败的autoconf测试而被这个问题所吸引。包。但是,我会毫不犹豫地推荐一个尚未经过测试的程序,用于autoconf测试。在实践中,我认为假设除非我们有一个公认的异常,例如Debian&#34; hppa&#34;架构。

答案 4 :(得分:0)

在调用子例程的Linux(或其他操作系统)进程中,局部变量的内存来自进程的堆栈区域。任何动态分配的内存(使用malloc,new等)都来自进程的堆区域。在递归期间,本地存储器在函数调用期间从堆栈区域分配,并在函数执行完成时被清除。

存储器的最低地址位于底部,最高位于顶部。以下是使用快速C代码查找递归中堆栈增长方向的步骤。

#include <stdio.h>

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