1

由于堆栈向下增长,即朝着数字较小的内存地址增长,为什么这样做&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 运算符时&)。这样做只是为了“让事情顺利进行”吗?

4

4 回答 4

8

如果我错了,请纠正我,但我想这是 C 创造者的设计决定

它不是 C 语言设计的一部分,也不是 C++ 设计的一部分。事实上,这些标准不存在所谓的“堆”或“栈”内存。

这是一个实现细节。每种语言的每种实现都可能以不同的方式执行此操作。


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

对于它的价值,您的示例程序在我的系统上输出三个“假”计数。

于 2019-09-03T14:23:14.873 回答
7

编译器生成的代码不是按顺序为每个单独的变量分配空间,而是为这些局部变量分配一个,因此可以在该块中安排它们,但它可以选择。

于 2019-09-03T14:13:24.670 回答
1

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

于 2019-09-03T14:15:09.760 回答
1

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

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

一个简单的跨平台示例(它在 gcc 和 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

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

这是 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使用堆栈来传递所有参数的调用约定:即使那样,值7也不8在变量的堆栈位置。被调用时直接压入栈other中,事先不存在于栈中:

gcc -m32 -fomit-frame-pointer -Os

test:
        sub     esp, 24
        push    7
        call    other
        push    8
        call    other
        add     esp, 24
        ret
于 2019-10-11T14:29:59.110 回答