2012-02-08 43 views
8

以下声明是什么意思?编译时为什么编译器不知道局部变量的地址?

本地和动态分配的变量必须在源文件编译

我曾经以为,局部变量被分配在编译时的地址不是由编译器知道地址,但该地址可在函数调用期间它会超出范围,然后再次进入范围。但是上面的语句说本地变量的addresess是编译器不知道的。那么如何分配局部变量?为什么在编译时可以知道全局变量的地址?

另外,你可以请提供一个很好的链接来阅读局部变量和其他分配的方式吗?

在此先感谢!

+5

从某种意义上说,只有在OS加载程序并在MMU中设置虚拟内存映射之后,才会知道地址。编译器知道布局,即事物出现的顺序,但一切都是从某个基地址开始测量的。对于静态存储持续时间,它是从二进制文件的加载地址开始测量的。对于自动存储持续时间,它是从堆栈指针测量的。动态存储的地址在运行时确定,但成员存储在与动态地址偏移处。 – 2012-02-08 02:56:23

+0

@Ben:这是一个完全不同的问题。 – 2012-02-08 04:04:19

回答

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副本。尽管本书的大部分内容涉及其他编译器构建技术,但本书的很大一部分内容都致力于实现代码生成和可用于改进生成代码的优化。

希望这会有所帮助!

+5

*自动*,不*本地*(可能是静态的) – 2012-02-08 03:06:40

+0

@ BenVoigt-我同意。我避免使用这个术语,因为大多数初学程序员学习“本地”和“全局”而不是“自动”,“静态”和“动态”,因为前者更通用,后者通常只适用于C/C++ 。 – templatetypedef 2012-02-08 03:09:32

+0

@templatetypedef:我想我找到你了,但是请你指出user966379答案中的错误。他说:“在编译时,源代码被转换成机器语言代码,所以没有地址是已知的。”以上 。 – 2012-02-08 11:50:11

0
  1. 动态变量的地址由于预期的原因未知,因为它们是从内存池中动态分配的。
  2. 本地变量的地址是未知的,因为它们驻留在 “堆栈”内存区域。基于代码流的运行时间条件,程序的堆栈绕开 - 展开可以延迟。

例如:

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条件可以是truefalse并将结果仅在运行时是已知的。基于int iint j将发生在适当的偏移堆栈上。

+0

我想你回答说“局部变量地址在编译时不知道”。但我问过为什么编译器本身不知道地址。 – 2012-02-08 11:53:38

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是静态的)。

+0

'Klass <&y>'在第二段中是合法的,很可能你的编译器没有完全实现标准。 “非模板非模板模板参数*的*模板参数*应该是以下之一:”...“一个常量表达式(5.19),指定具有静态存储持续时间和外部对象的地址或内部链接或“...(第14.3.2节'[temp.arg.nontype]')这是C++ 03的一个重大变化,它需要外部链接。 – 2012-02-08 04:10:36

+0

@BenVoigt我想在C++ 03模式下编译。我很高兴看到在C++ 11中对模板的一些限制已经减少了。 – 2012-02-08 05:19:08

0

这是一个很好的问题。

执行代码时,程序被加载到内存中。然后局部变量获取地址。在编译时,将源代码转换为机器语言代码,以便可以执行。