如果堆栈在数值较低的地址增长,为什么指针比较会反转它?

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

由于堆栈向下增长,即朝向数值较小的内存地址增长,为什么 &i < &j 是正确的。如果我错了,请纠正我,但我想这是 C 创建者的设计决定(C++ 维护)。但我想知道为什么。

同样奇怪的是,堆分配的对象 pin 位于数值上比堆栈变量更高的内存地址,这也与堆位于数值上小于堆栈的内存地址这一事实相矛盾(并且向上增加)。

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

到目前为止我是对的吗?如果是的话,为什么 C 设计者扭转了这种情况,即数值较小的内存地址显得更高(至少当您比较指针或通过 addressof 运算符 & 时)。这是刚刚 'to make things work' 完成的吗?

编译器生成的代码不是按顺序为每个单独的变量分配 space,而是为这些局部变量分配一个 ,因此可以对它们进行排列在该块内,无论它选择什么。

通常,一个函数的所有局部变量在函数入口时分配为一个块。因此,如果您比较在外部函数中分配的局部变量的地址与在内部函数中分配的局部变量的地址,您只会看到堆栈向下增长。

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

它不是C语言设计的一部分,也不是C++的设计。事实上,这些标准不承认"heap"或"stack"内存。

这是一个实现细节。每种语言的每种实现方式可能有所不同。


指向不相关对象(例如 &i < &j(void*)pin > (void*)pi 的指针之间的有序比较具有未指定的结果。两者都不能保证小于或大于另一个。

对于它的价值,您的示例程序在我的系统上输出了三个 "false"。

这真的很简单:这样的堆栈是一个实现细节。 C 和 C++ 语言规范甚至不需要引用它。符合标准的 C 或 C++ 实现不需要使用堆栈!如果它确实使用堆栈,语言规范仍然不能保证其地址以任何特定模式分配。

最后,变量可以存储在寄存器中,或者作为代码文本中的立即值,而不是数据存储器中。然后:取这样一个变量的地址是一个自我实现的预言:语言规范将值强制到一个内存位置,并且它的地址提供给你 - 这通常会破坏性能,所以不要取地址你不需要知道地址。

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

任何合理的编译器都不会不必要地将 xz 放入内存中。它们要么存储在寄存器中,要么作为立即值压入堆栈。

这是 gcc 9.2 的 x86-64 输出 - 请注意,不存在内存加载和存储,并且存在尾调用优化!

gcc -m64 -Os

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

在 x86 上,我们可以强制使用堆栈传递所有参数的 stdcall 调用约定:即便如此,值 78 也永远不会在堆栈位置一个变量。调用other时直接入栈,之前栈中不存在:

gcc -m32 -fomit-frame-pointer -Os

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