为什么编译器会创建一个“两次”变量?

时间:2010-06-30 21:52:00

标签: c windows compiler-construction

我知道这是一个更“重”的问题,但我认为它也很有趣。它是of my previous questions about compiler functions的一部分,但是我回答的非常糟糕,许多人回答了我的第一个问题,所以它是:

因此,如果我的知识是正确的,现代Windows系统使用分页作为切换任务的方式,并保证每个任务都在内存中占有一席之地。因此,每个流程从0开始都有自己的位置。

当多任务处理生效时,内核必须将所有重要的寄存器保存到任务堆栈中我相信比保存当前堆栈指针,更改页面条目以切换到另一个进程的物理地址空间,加载新进程堆栈指针,弹出保存的寄存器并继续通过调用poped指令指针地址。

对于这个不错的功能(分页)而言,每个进程都认为它具有良好的平坦内存。因此,没有远端跳转,远指针,内存段或数据段。一切都很好而线性。

但是,当流程没有更多细分时,为什么编译器仍然在堆栈上创建变量,或者直接在其他内存空间中创建变量,而不是直接在程序代码中创建变量?

让我举个例子,我有一个C代码:int a=10;

被翻译成(英特尔语法):mov [position of a],#10

但实际上,你实际上在RAM中的字节数比需要的多。 Becouse,前几个字节接受actuall指令,在该指令完成后,有一个包含值10的新字节。

为什么,而不是这个,当不需要切换任何段(从而减慢处理速度)时,不仅仅是10的值直接编码到这样的程序中:

xor eax,eax //just some instruction
10 //the value iserted to the program
call end //just some instruction

Becouse编译器知道每条指令的确切位置,当使用该变量操作时,它只会使用它的地址。

我知道,const变量会这样做,但是当你无法改变它们时,它们不是真正的变量。

我希望我能很好地解释我的问题,但我仍然在学习英语,所以请原谅我的相互错误甚至语义错误。

编辑:

我已经阅读了你的答案,似乎基于那些我可以修改我的问题:

所以,有人告诉我,全局变量实际上就是那个直接附加到程序中的值,我的意思是,当变量是全局的时候,它是在程序结束时,还是像当地的变量那样创建的执行,而不是直接在堆栈上堆栈?

如果第一种情况 - 附加到程序本身,为什么甚至存在局部变量?我知道,你会告诉我因为递归,但事实并非如此。当你调用函数时,你可以在堆栈上推送任何内存空间,因此那里没有程序。

我希望你理解我,当某些指令在堆栈上创建一些值(甚至为0)时,总是会无法使用内存,因为你需要在程序中为该指令而不是实际的var。像这样:push #5 //instruction that says to create local variable with integer 5 并且这个指令只是让数字5在堆栈上。请帮帮我,我真的很想知道为什么会这样。感谢。

6 个答案:

答案 0 :(得分:5)

考虑:

  • 如果一个例程被递归地调用(甚至间接地在一个递归的正确解析器中)或者来自多个线程,则局部变量可能同时存在多个,并且这些情况发生在相同的记忆背景
  • 将程序存储器标记为不可写,并且堆栈+堆为非可执行文件是对某些类别的攻击(堆栈粉碎......)和某些操作系统使用的 的小而有用的防御(但我不知道windows是否会这样做)

您的提案不允许出现上述任何一种情况。

答案 1 :(得分:4)

  

因此,没有远端跳转,远指针,内存段或数据段。一切都很好而线性。

是和否。不同的程序段具有不同的目的 - 尽管它们位于扁平虚拟内存中。例如。数据段是可读写的,但您无法执行数据。代码段是可读和可执行的,但您无法写入。

  

为什么编译器仍然在堆栈上创建变量,而不是直接在程序代码中创建变量?

简单。

  1. 代码段不可写。出于安全原因首先。第二, 大多数CPU不喜欢将代码段写入其中 打破了许多用于加速执行的现有优化。
  2. 函数的状态必须是函数私有的 像递归和多线程这样的东西。
  3.   

    不仅仅是10的值直接编码到这样的程序中

    现代CPU预取指令以允许并行执行和乱序执行等操作。将垃圾(作为垃圾的CPU)放入代码段将简单地减少(或消除取消)技术的影响。他们应对CPU在过去十年中所取得的性能提升所占的份额负责。

      

    当不需要切换任何段

    因此,如果没有切换段的开销,为什么然后将其放入代码段?将它保存在数据段中没有问题。

    特别是在只读数据段的情况下,将程序的所有只读数据放在一个位置是有意义的 - 因为它可以由正在运行的应用程序的所有实例共享,从而节省了物理RAM。

      

    Becouse编译器知道每条指令的确切位置,当使用该变量操作时,它只会使用它的地址。

    不,不是真的。大多数代码都是可重定位的或位置无关的。当操作系统将代码加载到内存中时,代码将使用实际内存地址进行修补。实际上,使用特殊技术来实际避免修补代码,以便所有正在运行的应用程序实例也可以共享代码段。

    ABI负责定义编译器和链接器应该如何执行以及程序可由执行操作系统执行的操作。我还没有看过Windows ABI,但很容易找到Linux使用的ABI:搜索“AMD64 ABI”。即使阅读Linux ABI,也可以回答一些问题。

答案 2 :(得分:3)

您所谈论的是优化,这就是编译器的业务。如果没有任何东西改变那个值,并且编译器可以解决这个问题,那么编译器可以完全自由地做你所说的(除非a被声明为volatile)。

现在,如果您说您看到编译器不是那样做,并且您认为应该这样做,那么您必须与编译器编写者交谈。如果您使用的是VisualStudio,他们的地址是One Microsoft Way,Redmond WA。祝你好运敲门。 : - )

答案 3 :(得分:3)

  

为什么不将10的值直接编码到这样的程序中:

xor eax,eax //just some instruction  
10 //the value iserted to the program  
call end //just some instruction  

全局变量的存储方式。但是,它们不是被困在可执行代码(现在很混乱,而是not even possible)的中间,而是存储在内存中的程序代码之后(至少在Windows和Linux中),在所谓的 .data部分

如果可以,编译器会将变量移动到.data部分以优化性能。但是,有几个原因可能没有:

  • 某些变量不能变为全局变量,包括类的实例变量,传递给函数的参数(显然),以及递归函数中使用的变量。
  • 变量仍然存在于某个内存中,但仍然必须有代码才能访问它。因此,内存使用率不会改变。实际上,在x86(“Intel”)上,根据this page引用局部变量的指令:

    mov eax, [esp+8]
    

    以及引用全局变量的指令:

    mov eax, [0xb3a7135]
    

    都采用1( 1!)时钟周期。

    唯一的好处是,如果每个局部变量都是全局的,那么就不必为堆栈腾出空间用于局部变量。

  • 将变量添加到.data段实际上可能增加可执行文件的大小,因为该变量实际上包含在文件本身中。

  • 作为caf mentions in the comments,基于堆栈的变量仅在函数运行时存在 - 全局变量在整个程序执行期间占用内存

    < / LI>

答案 4 :(得分:0)

不太确定你的困惑是什么?

int a = 10; 表示在内存中创建一个点,并将值10放在内存地址

如果你想要 10

#define a 10

虽然更典型的是

#define TEN 10

答案 5 :(得分:0)

变量具有存储空间,可以进行修改。将它们粘贴在无法修改的代码段中是没有意义的。

如果你的代码有int a=10甚至const int a=10,编译器就无法转换引用'a'的代码直接使用常量10,因为它无法知道'a'是否可能在它背后改变(甚至可以改变const变量)。例如,如果你有一个指向'a'的指针,那么可以在没有编译器知道的情况下改变单向'a'。指针在运行时没有固定,因此编译器无法在编译时确定是否存在指向和修改“a”的指针。