4

How do I find in C whether a stack is progressing in forward or reverse direction ? WIll this work?

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");
Shweta
  • 5,198
  • 11
  • 44
  • 58
Neel
  • 9,913
  • 16
  • 52
  • 74
  • 1
    If this is homework you should tag it as such. – Chris Eberle Jun 21 '11 at 01:12
  • 1
    You forgot to close your " and you also forgot a ; :O – maaudet Jun 21 '11 at 01:13
  • Nope. But C puzzles I am trying to crack. – Neel Jun 21 '11 at 01:14
  • 3
    Does C itself even *know* about "the stack"? I don't remember anything specifically addressing it; AFAIK you can have a C implementation that doesn't use a stack at all, or uses it radically differently than people normally use it. – cHao Jun 21 '11 at 01:17
  • 2
    Dead right, @cHao, ISO does not require a stack at all. See http://stackoverflow.com/questions/664744/what-is-the-direction-of-stack-growth-in-most-modern-systems/664779#664779 for some interesting reading on various systems, including one where a linked list emulates a stack – paxdiablo Jun 21 '11 at 01:25
  • the reality is, most C compilers use a stack because they are super easy and fast :) so its a valid question.... no need to be pedantic – Keith Nicholas Jun 21 '11 at 01:30
  • @cHao, @paxdiablo, in the practical case, the LLVM intermediate representation (used by clang/clang++) uses registers to represent all values. The stack usage is generated by the backend. @Keith Nicholas, the stack is much slower than registers, and is useful only in large functions with lots of locals or large locals, or when you need to pass pointers (because you can't address a register). – zneak Jun 21 '11 at 01:31

5 Answers5

15

To be reliable, one would have to find the difference between two function calls.

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);

Note that this won't give you an answer about C, but about your compiler.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Even there, if your compiler inlines `func`, we're back to square one. This would probably work in debug code with no optimizations, if you just want to know for the sake of curiosity, but I wouldn't rely on it for production code. – zneak Jun 21 '11 at 01:27
  • Yes. An implementation may order local variables however it wishes _within_ a stack frame for efficiency: http://stackoverflow.com/questions/6079063/why-is-a-function-call-rather-than-variable-addresses-used-to-detect-stack-grow/6079087#6079087 and, of course, the stack doesn't have to exist at all :-) – paxdiablo Jun 21 '11 at 01:27
  • I'm not saying it will always work, but: @zneak, Inlining a recursive function would be quite a feat. @paxdiablo, According to your on link, my variable would have to exist on the stack. – ikegami Jun 21 '11 at 01:33
  • 2
    (p < &i) invokes undefined behavior. The result of applying relational operators to pointers is defined only if the pointers point to objects within the same array or structure. – sigjuice Jun 21 '11 at 03:37
  • @sigjuice, the whole concept presumes the vars are in the same stack, so I wouldn't be worried about that. That said, I appreciate the info. – ikegami Jun 21 '11 at 04:26
  • 1
    @ikegami Why not use intptr_t or uintptr_t provided by stdint.h for comparison? So, instead of "else if ( p < &i )", you can use "else if ( ((intptr_t) p) < ((intptr_t) &i) )" to avoid UB, isn't it? – ZeZNiQ Aug 30 '20 at 17:27
9

You cannot. In your code, (&k > &j) invokes undefined behavior behavior. Pointer comparison with relational operators is not defined unless the pointers point to objects within the same array (or one object beyond the end of the array).

Whether a stack exists is dictated by your implementation. Undefined behavior cannot predict implementation details.

The ISO C standard does not mention the word "stack" even once. A stack might not even exist. The memory used by function invocations to keep local variables might not even be contiguous.

sigjuice
  • 28,661
  • 12
  • 68
  • 93
3

It has already been pointed out that a C execution environment does not necessarily use a stack (function activation frames may be allocated on a heap). So let us assume that we have a system that does use a stack for automatic variables. Then we might be able to determine the stack direction by comparing the addresses of variables from two different activation frames. However, there are two problems with this approach:

  1. The comparison is illegal. If the compiler can tell that a comparison is illegal, or that the comparison, if it is legal, must have a particular result, then it might not generate code to perform the comparison. For example, if you compare two pointers to type T and the program contains no arrays of type T[] of length greater than 1 then the compiler might deduce that the pointers must compare equal.
  2. How can we be sure that the variables really are in different activation frames? A compiler could convert some automatic variables into static variables and even recursive functions may be inlined (GCC inlines a simple recursive factorial function).

The first problem is insoluble if we have a symbolic execution environment that can detect an illegal pointer comparison at run time. So let us assume that we have a conventional optimising compiler that represents pointers with bare machine addresses (when they cannot be optimised away).

Thinking about all this I initially got distracted by the idea of converting the pointers into integers (C99's uintptr_t). But this is a red herring, I think. Firstly, comparing the integers might not give the same result as comparing the original pointers so you would have to convert them back anyway. Secondly, we are not trying to conceal from the compiler that we are comparing pointers; we are only trying to conceal from the compiler which pointers we are comparing.

I found it helped to consider the second problem first: how can we ensure that we have pointers to variables in different activation frames?

Let us reject the idea of putting one function in a separate library or dynamically loaded module: that would be non-portable, and if we are going to be non-portable then we might as well print out the pointers with printf("%p\n", p) and compare them with shell utilities. Apart from being non-portable that would also be no fun at all.

To force the compiler to generate code with local variables in activation frames we could have a function that is recursive to a depth that cannot be determined at compile time with a local variable that is potentially live across a recursive call, and so on. In short, we want to make it very hard, preferably impossible, for the compiler to determine what is going to happen at run time.

There are various ways we could make the execution predictable for us but unclear to the compiler. We could use complex mathematics or a pseudorandom number generator. However, it is probably good enough just to make it potentially depend on the command-line arguments, with the behaviour we want being the default behaviour with no arguments (hoping that no real-world compiler optimises a program by doing symbolic interpretation with the assumption that it will be executed with no arguments). So we could have the sequence of operations to be performed specified explicitly in argv[1] and the program would be a kind of mini-interpreter. With that approach I think I can answer the original question with the following program that tries to be portable by using no header files or library functions:

// 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;
}

Here is a longer version with comments and trace output to explain how it works:

// 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;
}

Note that I have hardly tested this program!

I was originally drawn to this problem by a failing autoconf test in the Debian "librep" package. However, I would hesitate to recommend an as yet untested program like this for use in an autoconf test. In practice I would guess it is safer to assume that all stacks are descending unless we have a recognised exception, such as Debian's "hppa" architecture.

  • Interesting...but...it's quite a lot of work to do for something when the very premise upon it's based (that the compiler has any obligation on the relative memory positions of how it organizes independent entities like local variables or function frames) is flawed from inception. It would be a rare project for which such an auto-detection strategy is justified, vs asking for explicit parameterization ("tell me what you know about your compiler, if you know"). – HostileFork says dont trust SE Mar 14 '17 at 03:46
3

This is not a characteristic easy to determine in C alone because your compiler may perform various optimizations that can break such tests. You would probably be better off with an assembly function.

In other words, your function could work, but it's not sure. And if it doesn't work, it won't report an error: instead, you'll get an incorrect result, and no way to tell. The stack, and the handling of calling conventions, are about the only two low-level things that C manages to hide.

My x86 assembler is rusty, but off my head, this (Intel syntax) assembly function could give the correct results. Its C prototype would be int getGrowthDirection(); it returns a positive number if the stack grows forward and a negative number if the stack grows in reverse direction.

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

Note that this function is next to useless, as assembly requires you to know the platform you're targetting, and if you know the platform you're targetting, then you should know the stack growth direction.

zneak
  • 134,922
  • 42
  • 253
  • 328
-1

In a Linux (or other Operating System) process when a subroutine is called, the memory for local variables comes from stack area of the process. Any dynamically allocated memory (using malloc, new, etc.) comes from the heap area of the process. During recursion local memory is allocated from stack area during function call and get cleared when the function execution is done.

The memory is being represented with lowest address being at the bottom and highest being at the top. Here are the steps to find the direction of stack growth in recursion using a quick C code.

#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);
}

out put on MAC

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

output on ubuntu

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

The stack is growing downwards on these specific setups as memory addresses are reducing. This depends on the architecture of the system and may have different behavior for other architectures. 0x7fff6f9e8868

Megharaj
  • 1,589
  • 2
  • 20
  • 32
  • The point people are trying to make is that this test isn't reliable. Nothing in the C standard makes this kind of promise. As a consequence, a lot of weirder compilers that implement the standard can give you unpredictable results (like emscripten building to JavaScript). Even "normal" compilers that "usually do the expected thing" without optimization can have optimization levels that make them seem as weird as a "weird" compiler. – HostileFork says dont trust SE Mar 14 '17 at 03:57