Gcc 静态分配程序编译时间长

Gcc 静态分配程序编译时间长,gcc,Gcc,如果有人能告诉我为什么要编译这个程序,我将不胜感激: double data[123456789]; int main() {} 所需时间是编译此文件所需时间的10倍: int main() { double* data=new double[123456789]; } 当两者都使用以下工具编译时: $ g++ -O0 可执行文件的大小几乎相同 我在Ubuntu 10.04上使用GCC4.4.3 谢谢。动态分配 第二个程序在运行时分配内存;从编译器的角度来看,编译以下各项之间没

如果有人能告诉我为什么要编译这个程序,我将不胜感激:

double data[123456789];  
int main() {}
所需时间是编译此文件所需时间的10倍:

int main() {
    double* data=new double[123456789];
}
当两者都使用以下工具编译时:

$ g++ -O0
可执行文件的大小几乎相同

我在Ubuntu 10.04上使用GCC4.4.3


谢谢。

动态分配

第二个程序在运行时分配内存;从编译器的角度来看,编译以下各项之间没有真正的区别:

double *data = new double[123456789];
double *data = malloc(123456789);
double data  = sqrt(123456789);
它们都做不同的事情,但编译器需要做的只是生成一个带有固定参数的外部函数调用。如果使用
g++-S
生成程序集,则可以看到:

.text
main:
    subq    $8, %rsp          /* Allocate stack space. */
    movl    $987654312, %edi  /* Load value "123456789 * 8" as argument. */
    call    _Znam             /* Call the allocation function. */
    xorl    %eax, %eax        /* Return 0. */
    addq    $8, %rsp          /* Deallocate stack space. */
    ret
这对于生成任何编译器和链接器都很简单

静态分配

不过,正如您所注意到的,您的第一个程序有点捉弄人。如果我们看一下装配,我们会看到一些不同的情况:

.text
main:
    xorl    %eax, %eax        /* Return 0. */
    ret

.bss
data:
    .zero   987654312         /* Reserve "123456789 * 8" bytes of space. */
当程序首次启动时,生成的程序集要求保留
123456789*sizeof(double)
字节的空间。当这被组装并随后链接时(这发生在后台,即您只需运行
g++foo.c
),链接器
ld
将实际分配内存中的所有保留空间。这就是时间的方向。如果在运行
g++
时运行
top
,您将看到
ld
占用了大量系统内存

减少了可执行文件的大小

一个合理的问题可能是“如果在链接时保留了内存,为什么我的可执行文件最终不是很大?”。答案隐藏在程序集中的
.bss
标记中。这告诉链接器下面定义的数据不应该存储在最终的可执行文件中,而是在运行时分配给零

这就给我们留下了以下一系列步骤:

  • 汇编程序告诉链接器它需要创建一段1GB长的内存


  • 链接器继续分配内存,准备将其放入最终的可执行文件中

  • 链接器意识到该内存位于
    .bss
    部分,并被标记为
    NOBITS
    ,这意味着数据仅为0,不需要物理地放入最终的可执行文件中。它避免了写出1GB的数据,而只是扔掉分配的内存

  • 链接器将编译后的代码写入最终的ELF文件,生成一个小的可执行文件

  • 一个更智能的链接器可能能够避免上面的步骤2和步骤3,从而使编译速度更快。实际上,像您这样的场景在实践中出现的频率不足以让这样的优化变得有价值

    动态与静态分配

    如果您试图确定在您的程序中实际使用上述哪一项(动态分配与静态分配),以下是一些想法:

    • 链接器将需要使用与最终程序一样多的内存(外加一点)。如果您想静态分配4GB的RAM,您的链接器将需要4GB的RAM。这并不是链接器的工作方式,而是它们的实现方式

    • 当分配大量内存时,动态地分配内存可以让您更好地处理错误(在屏幕上显示一条用户友好的消息,说明您没有足够的内存,而不是仅仅通过操作系统发送给用户的消息无法加载可执行文件)

    • 动态分配内存允许您根据实际需要选择分配多少内存。(如果
      数据
      是缓存,用户可以选择缓存大小,如果它存储中间结果,您可以根据问题调整大小,等等。)

    • 动态分配内存允许您在以后释放内存,前提是您的程序需要继续运行,并在使用完内存后执行更多工作


    最后,如果以上几点都不重要,并且您可以处理更长的编译时间,那么这可能不会太重要。静态分配内存可以简单得多,对于内存量较小的应用程序或一次性应用程序来说,这通常是正确的方法。

    非常有趣的观察结果。我注意到clang++(来自LLVM的2.8版本)具有完全相同的行为。我怀疑这段时间花在了链接器上,甚至是汇编程序上,而不是编译器上……一个快速测试证实了这段时间花在了链接器上(
    g++-c-O0
    对于这两个源几乎都是即时的)“链接器继续工作并分配这个内存”--这是LD中的一个bug,它已经被修复:
    GNU LD(适用于Ubuntu的GNU Binutils)2.22
    显示了问题,而
    2.24.51.20131021
    没有。