1

I write such code

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

and get result:

46372e18, 46372e1c
d062ec20, d062ec24

Is not that that stack grow down and heap up?

jfxu
  • 690
  • 1
  • 5
  • 15

5 Answers5

8

To learn which way "the"1 "stack"2 grows, you must make at least one function call. Something like this, for instance:

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

You must do it this way because most compilers allocate all of the stack space for a function call all at once, upon entry, in what's called a "stack frame", and organize the space within as they see fit. Therefore, comparing the address of two variables local to the same function doesn't tell you anything useful. You must also take care to compile this program with "inlining" turned off; if the compiler is allowed to merge stack_probe into main, then it will all be one stack frame again and the results will be meaningless. (Some compilers let you control inlining on a function-by-function basis, but there's no standard way to do that as far as I know.)

The number printed by this program is "unspecified" by the C standard3 (that means "it will print some number, but the standard doesn't require it to be any particular number"). However, on almost all computers you are likely to be able to get your hands on today, it will print a negative number, and that means the stack grows downward. If you manage to run it on a PA-RISC machine running HP-UX (it might not even compile, unfortunately; I don't remember if HP-UX ever had a C99-conformant library) it will print a positive number, and that means the stack grows upward.

There have been computers on which the number printed by this program wouldn't mean anything, because their equivalent of a "stack" was not necessarily a contiguous block of memory. Look up "split stacks" for the simplest version of that.

The "heap," incidentally, does not necessarily grow up or down. Consecutive calls to malloc return pointers with no meaningful relationship to each other, always.


1 There can be more than one stack, for instance when threads are in use.

2 Fun fact: the word "stack" appears nowhere in the C standard. There is a requirement to support recursive function calls, but how the implementation manages that is left completely up to the implementation.

3 Also, it is implementation-defined whether this program will compile, because the implementation is not required to provide intptr_t and uintptr_t. But if I hadn't used those types, the program would have undefined behavior ("it's allowed to do anything at all, including crashing and having all of its code deleted") because you're only allowed to take the difference of two pointers when they point into the same array, which these don't.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • It may be possible that the compiler may not generate a call for such a function. – Jean-Baptiste Yunès Sep 04 '17 at 14:32
  • @Jean-BaptisteYunès Yes, that's why I said it needs to be compiled with inlining turned off. – zwol Sep 04 '17 at 14:32
  • This may not be possible (1) some compilers may not have such a flag (2) even with such a flag it is not clear that some calls may not be "inlined" (3) such functions may not be inlined and called without pushing a stack frame. It is never required that a function call use the stack (except if recursion is involved), even if it is implemented as such very commonly. – Jean-Baptiste Yunès Sep 04 '17 at 14:43
  • @Jean-BaptisteYunès If I change the example code to use one level of recursion, will that satisfy you? A sufficiently smart compiler _could_ still squash it out, since the maximum call depth is still known at compile time, but there's no good way to get runtime-variable call depth into a test program like this... – zwol Sep 04 '17 at 18:50
  • No I think you (eventually) just have to mention the existing problem. That was just a remark. – Jean-Baptiste Yunès Sep 05 '17 at 05:25
3

All of this is not specified by the standard. The standard doesn't even mention stack and heap. Both of these are implementation details that isn't required by the standard.

Further you only have one dynamic object (malloc'ed object) and it will follow the normal layout for an array. So from that you can't say anything about how the heap grows on your system. If you want to try to see what your system does, you'll need at least two malloc'ed objects.

To print pointers use:

printf("%p, %p\n", (void*)&a, (void*)&b);
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
1

because of pointer arithmetic:

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

(note that printing pointer values requires %p format, other formats can "work" but they can break too)

address of m[1] is address of m[0] plus sizeof(int), that doesn't depend on where m is allocated (global or auto)

and here:

int a = 0;
int b = 0;

As opposed to structure members, the compiler may locate auto variables wherever it chooses relatively to each other. It may swap them, group the ones with lower alignment constraint, etc... So you're looking at an implementation detail here.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • [`%x` is used with an unsigned integer](http://en.cppreference.com/w/cpp/io/c/fprintf), isn't it? So it should fail on a 64 bit machine with 64 bit addresses and 32 bit integer, right? – Andre Kampling Sep 05 '17 at 07:14
  • I didn't focus on the way OP displays his addresses. It seems to work (I'll edit for completeness) and the info it returns sounds correct. – Jean-François Fabre Sep 05 '17 at 07:15
0

Yes, the stack grows down on virtually all modern systems. However, 1. that is an implementation detail, and 2. that is in terms of stack frames, not individual variables.

When your function gets called, it creates a stack frame on the stack, which is basically a region of stack memory that is used to hold the local variables. Where each function is placed within this stack frame, is entirely up to the compiler.

However, if you ran this code:

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

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

you would see that the address of b is smaller than the address of a: When foo() gets called, it creates a new stack frame, and b will be inside this stack frame, which will be below the stack frame allocated by main().

For the heap, there is no notion of direction whatsoever. Of course, most allocators will exhibit some patterns in how they allocate their memory, but as they all reuse memory that was free()'d, they all break their patterns at some point.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • You should cast to `void*` when using `printf()` with `%p`, see here: [printf(“%p”) and casting to (void *)](https://stackoverflow.com/questions/24867814/printfp-and-casting-to-void). – Andre Kampling Sep 05 '17 at 07:10
  • @AndreKampling Wow, learned something again. Crazy, though, how much the standard differs from real hardware at times... – cmaster - reinstate monica Sep 05 '17 at 08:47
0

As pointed out by many , C stranded doesn't specify anything about this.

However I want to touch on the different aspect , test you are doing is not sufficient to determine the heap is growing up-words or down-words.

Update the code as shown below and check the results;

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

On my System I can see that heap address "increasing" and stack address "decreasing"

In your code you dont have any function call so stack frame is only one.

Vagish
  • 2,520
  • 19
  • 32