If the stack grows at numerically lower address why does pointer comparison reverses this?

血红的双手。 提交于 2019-12-11 11:33:37

问题


Since the stack grows downwards, ie towards numerically smaller memory addresses why does &i < &j is true. Correct me if I'm wrong, but I'd imagine this was a design decision of C creators (that C++ maintains). But I wonder why though.

It is also strange that a heap-allocated object pin lies at numerically higher memory address than a stack variable and this also contradicts the fact that the heap lies at numerically smaller memory addresses than the stack (and increases upwards).

#include <iostream>

int main()
{
    int i = 5;                  // stack allocated
    int j = 2;                  // stack allocated
    int *pi = &i;               // stack allocated
    int *pj = &j;               // stack allocated

    std::cout << std::boolalpha << '\n';
    std::cout << (&i < &j) && (pi < pj) << '\n';            // true
    struct S
    {
        int in;
    };
    S *pin                      // stack allocated
        = new S{10};            // heap allocated
    std::cout << '\n' << (&(pin->in) > &i) << '\n';         // true
    std::cout << ((void*)pin > (void*)pi) << '\n';          // true
}

Am I right so far and if so why C designers reversed this situation that numerically smaller memory addresses appear higher (at least when you compare the pointers or through the addressof operator &). Was this done just 'to make things work'?


回答1:


Correct me if I'm wrong, but I'd imagine this was a design decision of C creators

It is not part of the design of the C language, nor C++. In fact, there is no such thing as "heap" or "stack" memory recognised by these standards.

It is an implementation detail. Each implementation of each language may do this differently.


Ordered comparisons between pointers to unrelated objects such as &i < &j or (void*)pin > (void*)pi have an unspecified result. Neither is guaranteed to be less or greater than the other.

For what it's worth, your example program outputs three counts of "false" on my system.




回答2:


The compiler generated code that isn't allocating space for each individual variable in order, but allocating a block for those local variables, and thus can arrange them within that block however it chooses.




回答3:


Usually, all the local variables of one function are allocated as one block, during function entry. Therefore you will only see the stack growing downward if you compare the address of a local variable allocated in an outer function with the address of a local variable allocated in an inner function.




回答4:


It's really rather easy: such a stack is an implementation detail. The C and C++ language spec doesn't even need to refer to it. A conforming C or C++ implementation does not need to use a stack! And if it does use a stack, still the language spec doesn't guarantee that the addresses on it are allocated in any particular pattern.

Finally, the variables may be stored in registers, or as immediate values in the code text, and not in data memory. Then: taking the address of such a variable is a self-fulfilling prophecy: the language spec forces the value to a memory location, and the address of that is provided to you - this usually wrecks performance, so don't take addresses of things you don't need to know the address of.

A simple cross-platform example (it does the right thing on both gcc and msvc).

#ifdef _WIN32
#define __attribute__(a)
#else
#define __stdcall
#endif

#ifdef __cplusplus
extern "C" {
#endif
__attribute__((stdcall)) void __stdcall other(int);

void test(){
    int x = 7; 
    other(x);
    int z = 8;
    other(z);
}

#ifdef __cplusplus
}
#endif

Any reasonable compiler won't put x nor z in memory unnecessarily. They will be either stored in registers, or will be pushed onto the stack as immediate values.

Here's x86-64 output from gcc 9.2 - note that no memory loads nor stores are present, and there's tail call optimization!

gcc -m64 -Os

test:
        push    rax
        mov     edi, 7
        call    other
        mov     edi, 8
        pop     rdx
        jmp     other

On x86, we can force a stdcall calling convention that uses stack to pass all parameters: even then, the value 7 and 8 is never in a stack location for a variable. It is pushed directly to the stack when other gets called, and it doesn't exist on the stack beforehand:

gcc -m32 -fomit-frame-pointer -Os

test:
        sub     esp, 24
        push    7
        call    other
        push    8
        call    other
        add     esp, 24
        ret


来源:https://stackoverflow.com/questions/57773450/if-the-stack-grows-at-numerically-lower-address-why-does-pointer-comparison-reve

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!