为什么书说“编译器为内存中的变量分配空间”?


18

为什么书会说“编译器为内存中的变量分配空间”。难道不是可执行文件吗?我的意思是,例如,如果我编写以下程序,

#include <iostream>
using namespace std;

int main()
{
   int foo;
   return 0;
}

并编译它,并获得一个可执行文件(将其命名为program.exe),现在,如果我运行program.exe,该可执行文件本身将命令为变量foo分配一些空间。不是吗 请解释为什么书中总是说“编译器会执行此操作...”。


11
您在谈论哪些书?
wirrbel 2013年

4
您的“相关问题”应该是一个单独的问题。
SShaheen


编译器生成执行此操作或正在执行的操作的代码。直接或间接。
old_timer

FYI stackoverflow.com/questions/7372024/...并注意编译器可以决定在内存中改变感知变量的位置的缘故对齐如:stackoverflow.com/questions/17774276/...
NoChance

Answers:


20

没错,当您的程序实际运行时,编译器本身已经消失了。如果它在其他计算机上运行,​​则该编译器甚至不再可用。

我想这是为了清楚地区分您自己的代码实际分配的内存。编译器将在您的程序中插入一些代码进行内存分配(例如,使用new,malloc或类似命令)。

因此,书籍经常使用“编译器执行此操作或该执行该操作”,通常是指编译器添加了一些代码文件中未明确提及的代码。确实如此,这并不是正在发生的事情。从这个角度来看,教程中提到的许多事情都是错误的,但需要相当详尽的解释。


是的,这就是我的信念。感谢您的快速回答!
和平编码员

12
编译器通过在编译期间将其替换为堆栈指针的偏移量来在堆栈上分配变量foo。它根本与mallocet等完成的堆分配无关。等
wirrbel

@holger:您的反对意见当然是正确的。但是,这样的堆栈空间仍然必须在程序启动时分配,然后才能使用(可能以多种方式发生,有时取决于CPU架构)。我试图找到一些细节,但这种方法并没有成功。
thorstenmüller2013年

2
我认为主线程的堆栈大小由链接器保留,然后由操作系统处理。对于自定义线程,它更类似于堆分配,即调用方可以在运行时确定大小。
wirrbel 2013年

4

它取决于变量。操作系统分配堆,程序将分配堆栈,编译器将为全局变量/静态变量分配空间,即它们内置在exe本身中。如果您分配1MB的全局内存,则exe大小至少会增加1MB


1
这不是这个问题的意思。
Philipp

2
实际上,它比这里列出的其他答案更接近问题。
wirrbel

@James Ah,这不是我的经验。例如,int test[256][1024]; int main(){ test[0][0]=2; return 0; } 此小程序分配了1MB的内存,但仅生成1.4 Kb的目标文件和8.4 Kb的可执行文件。但是,它应该使用正确数量的RAM。
Garet Claborn 2013年

1
难道不应该只是为全局变量存储的分配命令吗?如果您已使用int或char之类的原语对所有值进行了硬编码,则可执行文件的大小肯定会比添加的变量数量增加更多。例如int a1=1,a2=2,...一直到... , a1048576=1048576;直到我肯定会得到大于1mb的东西。
Garet Claborn 2013年

2
不论将数据放入exe的BSS部分中,都是
詹姆斯

4

编译器执行的操作是将您的代码编译为机器代码。您提到的是一个很好的例子,其中编译器只需要翻译。

例如,当你写

int foo;

您可以看到“我正在告诉编译器[ 在生成的输出中 ]请求计算机保留足够的ram以供以后可以引用的int编译器可能会使用资源ID或某种机制来跟踪机器代码,您将在文本文件中使用foo而不是编写汇编!万岁

因此,当编译器正在向所有目标处理器和设备写一封信(或者也许是一部小说/百科全书)时,您可能还会看到它。字母以二进制信号写入,通常可以通过更改目标将其转换为不同的处理器。任何“字母”和/或组合都可以发送各种请求和/或数据,例如请为程序员使用的此变量分配空间。


3

从字面意义上说“编译器分配内存”实际上可能并不准确,但这只是一个隐喻,暗示了正确的用法。

真正发生的是,编译器创建了一个分配自己的内存的程序。分配内存的不是程序,而是操作系统。

因此,真正发生的事情是,编译器创建了一个描述其内存需求的程序,而OS则采用该描述并使用它来分配内存。除了操作系统是一个程序,并且程序实际上不执行任何操作外,它们描述的是由CPU执行的计算。除了CPU实际上只是一个复杂的电子电路,而不是拟人化的小同形异体。

但是,将程序,编译器和CPU视为生活在计算机中的小人物是有道理的,并不是因为它们实际上是,而是因为这是一个非常适合人脑的隐喻。

一些隐喻在抽象的一个层次上很好地描述了事物,但在另一个层次上却表现不佳。如果您从编译器的角度考虑,则描述生成代码的行为是有意义的,当正在编译的程序实际上作为“分配内存”运行时,该代码将导致分配内存。它足够接近,以至于我们在考虑编译器的工作方式时,便有了正确的想法,而且它并没有花费太多时间而忘记了我们在做什么。如果我们尝试在已编译程序的运行级别上使用该隐喻,那么它会以一种奇怪的方式误导您,这就是您所注意到的。


0

由编译器决定将变量存储在何处-可以在堆栈中,也可以在自由寄存器中。不管编译器做出什么存储决定,都会生成访问该变量的相应机器代码,并且无法在运行时更改。从这个意义上说,编译器负责为变量分配空间,最终的program.exe在运行时只是像僵尸一样盲目地工作。

现在,不要将此与其他动态内存管理(例如malloc,new或可能是您自己的内存管理)混淆。编译器正在处理变量的存储和访问,但是它并不关心实际值在另一个框架/库中的含义。例如:

byte* pointer = (byte*)malloc(...);

在运行时,malloc可以返回任意数字,但是编译器并不关心,它关心的只是将该数字存储在何处。


0

更准确的措词是:“编译器告诉加载器为变量保留空间”

在C-ish环境中,变量的空间类型为三种:

  • 静态变量的固定块
  • 大的“自动”变量块通常称为“堆栈”。函数在进入时抓取一个块,并在返回时释放它。
  • 一个大的块,称为“堆”,是使用malloc()或类似的内存管理API在其中分配程序管理的内存的地方。

在现代OS上,堆内存实际上不会保留,而是根据需要分配。


0

是的,您是对的,在这种情况下(在函数中声明变量),您的书中的句子可能是不正确的:在函数中声明变量时,进入该函数时该变量会在堆栈中分配。无论如何,编译器应该优化这种情况:如果函数是非递归的(main()非常适合该函数),则可以(在BSS上)“分配”它的编译时间。

(如果您对变量所处的位置感到好奇,可以通过一种肮脏的方式检查它(如果您不想检查obj文件的结构,为什么呢?),因此您可以声明一些不同类型的变量:常量,静态,动态,- malloc()分配等,并显示其地址(使用%Xformatter printf()以提高可读性。驻留在堆栈中的变量将具有非常不同的内存地址。)


0

在运行时唯一要做的就是将堆栈指针增加一定量。因此,编译器事先决定:

  • 该功能需要多少堆栈空间。
  • 与堆栈指针的偏移量是每个单独的变量。

可以将其称为“分配”,但是,当然,在编译期间,它只能放置在编译器具有运行程序的模型中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.