以下陈述是什么意思?
本地和动态分配的变量具有编译源文件时编译器不知道的地址
我曾经认为局部变量在编译时被分配了地址,但是这个地址可以在超出范围时改变,然后在函数调用期间再次进入范围。但是上面的声明说编译器不知道局部变量的addresess。那么局部变量是如何分配的呢?为什么在编译时可以知道全局变量的地址?
另外,您能否提供一个很好的链接来阅读如何分配局部变量和其他变量?
提前致谢!
答案 0 :(得分:13)
上面的引用是正确的 - 编译器通常在编译时不知道局部变量的地址。也就是说,编译器可能知道从局部变量所在的堆栈帧的基础偏移,但是根据调用堆栈的深度,可能会在运行时转换为不同的地址。举个例子,考虑一下这个递归代码(顺便说一下,这不是一个好的代码!):
int Factorial(int num) {
int result;
if (num == 0)
result = 1;
else
result = num * Factorial(num - 1);
return result;
}
根据参数num
,此代码最终可能会进行多次递归调用,因此内存中会有几个result
副本,每个副本都包含不同的值。因此,编译器无法知道它们的全部内容。但是,result
的每个实例可能会从包含每个Factorial
调用的堆栈帧的基础偏移相同的数量,但理论上编译器可能会执行其他操作,例如优化此代码以便只有result
的一个副本。
通常,编译器通过维护堆栈帧的模型并跟踪堆栈帧中下一个空闲位置的位置来分配局部变量。这样,可以相对于堆栈帧的开始分配局部变量,并且当调用函数时,可以使用相对地址,结合堆栈地址,在特定堆栈帧中查找该变量的位置
另一方面,全局变量可以在编译时知道它们的地址。它们与本地人的不同之处主要在于程序中始终存在一个全局变量的副本。局部变量可能存在0次或更多次,具体取决于执行的方式。由于存在一个唯一的全局副本,编译器可以对其进行硬编码。
至于进一步阅读,如果您想对编译器如何布置变量进行相当深入的处理,您可能需要获取 Compilers: Principles, Techniques, and Tools, Second Edition 的副本Aho,Lam,Sethi和Ullman。虽然本书的大部分内容涉及其他编译器构造技术,但本书的大部分内容专门用于实现代码生成和可用于改进生成代码的优化。
希望这有帮助!
答案 1 :(得分:1)
在我看来,该声明并不是在讨论变量或范围的运行时访问,而是试图说一些更微妙的东西。
这里的关键是它的“本地和动态分配”和“编译时间”。 我相信该声明所说的是这些地址不能用作编译时常量。这与静态分配的变量的地址形成对比,静态分配的变量可以用作编译时常量。其中一个例子是模板:
template<int *>
class Klass
{
};
int x;
//OK as it uses address of a static variable;
Klass<&::x> x_klass;
int main()
{
int y;
Klass<&y> y_klass; //NOT OK since y is local.
}
似乎对不允许编译的模板有一些额外的限制:
int main()
{
static int y;
Klass<&y> y_klass;
}
然而,使用编译时常量的其他上下文可能能够使用&y
。
同样地,我希望这是无效的:
static int * p;
int main()
{
p = new int();
Klass<p> p_klass;
}
由于p的数据现在是动态分配的(即使p是静态的)。
答案 2 :(得分:0)
例如:
void bar(); // forward declare
void foo ()
{
int i; // 'i' comes before 'j'
bar();
}
void bar ()
{
int j; // 'j' comes before 'i'
foo();
}
int main ()
{
if(...)
foo();
else
bar();
}
if
条件可以是true
或false
,结果仅在运行时才知道。基于int i
或int j
将在堆栈上的适当偏移处发生。
答案 3 :(得分:0)
这是一个很好的问题。
执行代码时,程序被加载到内存中。然后局部变量获取地址。在编译时,源代码将转换为机器语言代码,以便可以执行