【问题标题】:Why do books say, “the compiler allocates space for variables in memory”?为什么书上说“编译器为内存中的变量分配空间”?
【发布时间】:2013-04-04 08:19:35
【问题描述】:

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

#include <iostream>
using namespace std;

int main()
{
   int foo = 0;
   cout<<foo;
   return 0;
}

并编译它,得到一个可执行文件(让它成为program.exe),现在,如果我运行program.exe,这个可执行文件将自己命令为变量foo分配一些空间。不会吗?请解释为什么书籍一直说,“编译器会做这个......做那个”,而实际上,编译的可执行文件会这样做。

在这个问题上添加另一个相关问题,为什么sizeof 被称为编译时运算符?它实际上不是一个运行时运算符吗?

【问题讨论】:

  • 这是一个不好的例子。 foo 没有被使用,编译时会被淘汰。
  • @Abyx Man,试着理解我的问题。重点不在于是否使用了 var。无论如何,我为你编辑了程序。
  • 伙计,实际上该编辑没有任何改变。 =)

标签: c++ compiler-construction runtime sizeof


【解决方案1】:

当我们聘请建筑师设计房屋时,他或她会定义房间的大小等,并告知工人(劳工)。工人相应地进行工作。但我们仍然会说“建筑师以这种方式建造房屋”而不是工人以这种方式建造房屋”。

工人只是在执行建筑师定义的步骤。编译器实际上会在运行时检查和定义要分配多少内存等等,然后这些指令就会被执行。

【讨论】:

    【解决方案2】:

    从技术上讲,创建空间本身的行为是在运行时完成的,但是编译器会在您的情况下为您的foo 确定堆栈上要保留多少空间变量。

    编译器知道int 类型的大小,因此可以生成正确的汇编指令,该指令将在堆栈上保留足够的空间,以便让foo 存在。

    如果您查看下面为您展示的程序生成的汇编程序(使用 MSVC2012),我已经对其中的一些进行了注释以向您展示发生了什么:

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    
    int main()
    {
    //Setup stack frame for main by storing the stack pointer from the calling function and 
    //reserving space for local variables and storing commonly used registers on the stack
    002E4390  push        ebp  
    002E4391  mov         ebp,esp  
    // reserve space for local variables, which is 204 bytes here, no idea why so much.
    // this is where the compiler calculated the size of your foo and added that to whatever else needs to be stored on the stack. Subtract from stack pointer (esp) because stack grows downward.  
    002E4393  sub         esp,0CCh  
    002E4399  push        ebx  
    002E439A  push        esi  
    002E439B  push        edi  
    002E439C  lea         edi,[ebp-0CCh]  // load effective address of [ebp-0CCh], which I suspect would be your foo variable into edi register
    002E43A2  mov         ecx,33h  
    002E43A7  mov         eax,0CCCCCCCCh  
    002E43AC  rep stos    dword ptr es:[edi]  //fill block of memory at es:[edi] with stuff  
       int foo;
       return 0;
    002E43AE  xor         eax,eax  //set eax to zero for return value
    }
    // restore everything back to how it was before main was called
        002E43B0  pop         edi  
        002E43B1  pop         esi  
        002E43B2  pop         ebx  
        002E43B3  mov         esp,ebp  
        002E43B5  pop         ebp  
        002E43B6  ret  
    

    【讨论】:

    • 这里一个有趣的问题是为什么编译器为局部变量分配 204 个字节,而它只需要 4 个字节。(您在这里使用了 VC++,但我注意到几个编译器似乎分配了更多比需要的。)
    • @JamesKanze,我想知道。我不知道为什么它似乎分配了这么多。
    • @JamesKanze 我问了一个问题:stackoverflow.com/questions/15806673/…,因为我很好奇为什么会这样。我在里面提到过你
    • @Abyx 我认为展示在这种情况下会发生什么并不重要。
    • @Abyx 你没抓住重点。完全地。这个想法是向 OP 描述编译器如何生成描述给定程序的内存要求的程序集。不是具体的,也不是描述优化的可能性和方法。
    【解决方案3】:

    这只是术语的松散使用。当然,编译器不会为程序分配内存。更准确的描述是它告诉运行时程序运行时要分配多少内存。

    在程序实际运行之前,它并不在内存中(除非它是动态加载的,但即使是在运行时发生的,所以超出了编译器的范围),所以没有内存可言。

    那些书谈论的是分配在编译时大小已知的变量,而不是大小未知的动态分配cin &gt;&gt; x; int * y = new[x];

    【讨论】:

      【解决方案4】:

      它说编译器为内存中的变量分配空间,因为否则您需要自己分配(并释放!)内存 new/malloc 等。

      【讨论】:

      • 那它就不是编译器了?你打算把它写在一张纸上? ; p
      【解决方案5】:

      当然编译器不会“为变量分配空间”。编译器生成一个代码,为内存中的变量分配空间。

      即如果你有

      int foo;
      foo = 1;
      

      在源代码中,编译器可能会生成类似

      的代码
      int* fooPtr = allocate sizeof(int)
      *fooPtr = 1;
      

      在 x86 架构中,allocate 通常是一条汇编指令:

      sub esp, 4    ; allocate 4 == sizeof(int) bytes on stack
                    ; now the value of "esp" is equal to the address of "foo",
                    ; i.e. it's "fooPtr"
      mov [esp], 1  ; *fooPtr = 1
      

      如果你有多个局部变量,编译器会将它们打包成一个结构并一起分配:

      int foo;
      int bar;
      bar = 1;
      

      将被编译为

      struct Variables { int foo; int bar; };
      Variables* v = allocate sizeof(Variables);
      v->bar = 1;
      

      sub esp, 4+4       ; allocate sizeof(Variables) on stack
      mov [esp + 4], 1   ; where 4 is offsetof(Variables, bar)
      

      【讨论】:

        【解决方案6】:

        编译器生成机器指令并计算出局部变量将占用的内存地址。每个局部变量都被赋予一个相对于栈顶的地址,例如foo 将被假定为位于内存地址stack_pointer。如果您有一个变量foo2,它将被放置在地址stack_pointer + 4,其中4 是int 的大小。

        当访问局部变量foo 时,编译器将替换stack_pointer 中保存的地址。硬件有一个特殊的stack_pointer 寄存器,它始终指向当前堆栈的顶部。

        编译器知道每个变量的大小,因为它负责查看structclass 声明并确定它在内存中的布局方式。所以sizeof 在编译时是已知的,并被视为常量表达式。像int 这样的原始类型已知具有一定的大小,例如sizeof(int) 是4。

        【讨论】:

          【解决方案7】:

          建议你阅读编译器构造。专注于存储阶段,您的查询将得到解决。

          程序编译后转换为目标代码,即汇编语言代码。高级语言程序的每一行都被翻译成许多汇编语言步骤。翻译后的程序被放入执行的汇编程序中。 编译器构造中存在存储分配阶段,该阶段转换为机器操作表(汇编级别的 MOT 指令)。这是为变量/寄存器分配空间的地方。 C++ 中也有寄存器修饰符关键字。

          【讨论】:

          • 这并没有提供问题的答案。要批评或要求作者澄清,请在他们的帖子下方发表评论 - 您可以随时评论自己的帖子,一旦您有足够的reputation,您就可以comment on any post
          • 程序编译后转换为目标代码,即汇编语言代码。高级语言程序的每一行都被翻译成许多汇编语言步骤。翻译后的程序被放入执行的汇编程序中。编译器构造中存在存储分配阶段,该阶段转换为机器操作表(汇编级别的 MOT 指令)。这是为变量/寄存器分配空间的地方。 C++ 中也有一个寄存器修饰符关键字。
          • 我知道 Nico O.。如果您碰巧不了解其他人的答案/观点,请在用户 cmets 中澄清。一旦作者了解了编译器的构建过程,他就会明白那是它的目标代码起作用了。编译器只是低级编程代码的代码生成器。
          • -1。你说的与问题无关。这也适用于 cmets。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多