有关已编译程序如何与操作系统交互的问题

时间:2011-02-14 06:27:40

标签: c++ c linux memory compilation

已经有一段时间了,我一直在使用C / C ++进行编程,但有些方面仍然无法实现。也许我没有读过写得好的权威材料。

(1)在Linux / Unix中,用户程序的大小是否有限制?程序可以拥有的最大堆栈大小?用户程序可以使用的堆中的最大内存量?

(2)我知道C可执行文件有数据部分,代码部分和&堆栈部分。如果程序进入许多递归调用,则需要大量的堆栈。这个预定义大小的堆栈是否会随着递归的增加而增长。在增长的情况下,程序的地址空间是否也必须动态增加?如果是这样,那会不会减慢程序的速度?

(3)同样,当程序mallocs在运行时将堆内存分配给程序时,需要将堆区域添加到程序的地址空间中?因此,在这种情况下,也需要更新程序的页表。我的理解是否正确?

(4)为什么2个文件(我打算组合形成单个可执行文件)不能有一个同名的全局变量。这将有助于揭示目标文件的外观。

增加:

我正在阅读http://www.open-std.org/jtc1/sc22/wg...docs/n1256.pdf的ISO C99标准。 它在第42页说:

6.2.2识别者的联系 1不同范围或同一范围内不止一次声明的标识符可以是 通过称为linkage的进程来引用相同的对象或函数。有 三种联系:外部,内部和无。

2在构成整个程序的翻译单元和库集中 具有外部链接的特定标识符的声明表示相同的对象或 功能。在一个翻译单元内,每个声明的内部声明 连接表示相同的对象或功能。每个声明的声明都没有 link表示一个独特的实体。

3如果对象或函数的文件范围标识符的声明包含存储类指定静态,则标识符具有内部链接。

4对于在a-scope范围内用存储类指定extern声明的标识符 如果先前的声明规定了内部或外部的联系,那么先前声明中的标识符的链接与先前声明中指定的链接相同,则可以看到该标识符的事先声明。如果没有先前的声明可见,或者先前的声明没有指定链接,则标识符具有外部链接。

5如果函数的标识符声明没有存储类指定符,则其链接的确定就像是使用存储类指定符extern声明的。如果对象的标识符声明具有文件范围并且没有存储级别的特定,它的链接是外部的。

在阅读之后,看起来我在两个源文件中声明了一个类似于说明int的变量。然后根据规则5和4都有外部链接,然后根据规则2,两者都应该引用同一个对象。那为什么编译器会产生问题。在标准中,暗示我们不能在2个源文件中声明这样,这应该抛出编译错误。

感谢。

2 个答案:

答案 0 :(得分:3)

回答你的问题 -

  1. 大多数操作系统使用虚拟内存让每个程序都认为它拥有所有地址空间。这意味着通常对程序大小的限制是系统中物理内存的数量减去通常为无效(思考NULL)指针和内核保留的少量内存。最大内存限制通常取决于平台,但在32位系统上,通常您的程序可以获得近4GB的内存,在64位系统上可以获得更多内存。当然,您还必须考虑磁盘的大小,这会限制您可以拥有的虚拟内存量。从理论上讲,你可以编写一个如此巨大的程序,以至于你无法将其放入内存中,但除非你使用嵌入式设备(这确实是一个问题),我怀疑这种情况会发生。

  2. 在大多数编程语言中,包括C和C ++,堆栈大小在编译时并不固定,而是从程序运行开始变小并增长。但是,堆栈增长的方式通常会使这个特别便宜 - 为了获得更多的空间,你只需要稍微颠倒堆栈指针。如果这会将您带入当前未分配给程序的内存中,操作系统通常会通过将页面与堆栈现在所在的虚拟地址相关联来为您分配内存,这比执行堆分配要快得多。从长远来看,这样做的成本通常可以忽略不计,所以不要气馁使用堆栈内存。有趣的是,一些较旧的编程语言,即FORTRAN的第一个版本,没有动态堆栈空间,因此递归是不可能的。几乎所有现代语言都消除了这些限制。

  3. 您是对的 - 当需要更多堆空间时,通常会调整页表以增加堆空间。许多内存分配器选择将大部分内存放入匿名内存映射文件中,以避免直接使用堆空间来实现此目的,但原理基本相同 - 更新页表以为新内存腾出空间。

  4. 如果在不同文件中有两个全局变量链接在一起,那么两个目标文件都将包含符号链接,表示需要引用具有该名称的变量,并且两个目标文件都将包含定义说它们提供了这个名称的符号。当您尝试将它们链接在一起时,链接器将注意到已在两个位置定义了相同的符号名称并将报告错误,因为它不确定它应该使用哪个作为该全局变量的“实例”。为了抵消这一点,至少在C中,您可以标记全局变量static以给它们内部链接。这使得符号不会全局导出,因此生成的目标文件可以在内部解析引用,也可以破坏名称,使其不与其他文件中的其他符号冲突。 C ++允许这一点以及匿名命名空间功能实现相同的效果。

  5. 希望这有帮助!如果有人发现错误或含糊不清,请告诉我,我很乐意纠正错误。

答案 1 :(得分:2)

  1. 是的,是的,是的。请参阅bash或man getrlimit中的“help ulimit”。

  2. 程序启动时设置堆栈大小,无法增加。

    表示,地址空间不会增加,因为您使用的堆栈比以前使用的堆栈多,但内存使用量会增加。

    当使用“拆分堆栈”时(例如在Google的Go中,但是正在进行工作以允许在gcc和其他语言的其他编译器中使用它),分配的附加内存不是“在堆栈上”和堆栈指针已调整。这是在调用函数时动态管理的,然后返回。

  3. 堆可能会根据需要增长。有关如何发生这种情况的简短概述,请参阅man sbrk,或查看various malloc implementations。你似乎理解它的要点。

  4. 因为,至少对于C和C ++,全局变量只能在整个程序中定义一次。两个转换单元(您可以将TU视为.o文件)可以使用同名的全局变量,但它只能定义一次,并且必须在其他TU中声明(使用正确的类型)。我不认为理解目标文件的细节会对此有所帮助,但是理解C ++中所谓的单一定义规则(ODR)的细节,或者它在您使用的任何语言中都是等效的,可能会有用。


  5. 关于编辑,您可能在两个TU中定义了一个int:

    int this_is_a_definition;
    

    你不能这样做。你应该在标题中声明它:

    extern int this_is_a_declaration;
    

    然后包含需要变量的标头,并在一个TU中定义变量。当然,如果您不想在不同的TU中使用相同的变量,那么您可能需要一个“内部”名称,例如您使用namespace-scope static或未命名的命名空间:< / p>

    static int local_to_this_TU;
    
    namespace {
      int another_local_to_this_TU;
    }