由于堆栈向下增长,即朝向数字较小的内存地址,为什么&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 运算符&
时(。这样做只是"让事情顺利进行"吗?
错了,请纠正我,但我想这是C创建者的设计决定
它不是C语言设计的一部分,也不是C++。事实上,这些标准没有识别的"堆"或"堆栈"内存这样的东西。
这是一个实现细节。每种语言的每种实现可能以不同的方式执行此操作。
指向不相关对象(如&i < &j
或(void*)pin > (void*)pi
(的指针之间的有序比较具有未指定的结果。不能保证两者都小于或大于另一个。
对于它的价值,您的示例程序在我的系统上输出了三个"false"计数。
编译器生成的代码不是按顺序为每个单独的变量分配空间,而是为这些局部变量分配一个块,因此可以按照自己的方式将它们排列在该块中。
通常,在函数输入期间,一个函数的所有局部变量都分配为一个块。因此,只有当您将外部函数中分配的局部变量的地址与内部函数中分配的局部变量的地址进行比较时,您才会看到堆栈向下增长。
这真的相当容易:这样的堆栈是一个实现细节。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 上,我们可以强制使用 stack 传递所有参数的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