如果有人能告诉我为什么要编译这个程序,我真的很感激:
double data[123456789];
int main() {}
比这个编译要长10倍:
int main() {
double* data=new double[123456789];
}
当两者都编译为:
$ g++ -O0
并且可执行文件的大小几乎相同。
我在Ubuntu 10.04上使用gcc 4.4.3。
谢谢。
答案 0 :(得分:25)
动态分配
你的第二个程序在运行时分配内存;从编译器的角度来看,编译以下任何内容之间没有真正的区别:
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
实际上将在内存中分配所有这些保留空间。这是时间的流逝。如果在top
运行时运行g++
,您会看到ld
占用了大量系统内存。
减少可执行文件大小
一个合理的问题可能是“如果在链接时保留内存,为什么我的可执行文件不会真的很大?”。答案隐藏在程序集中的.bss
标记中。这告诉链接器它下面定义的数据不应该存储在最终的可执行文件中,而应该在运行时分配给零。
这给我们留下了以下一系列步骤:
汇编程序告诉链接器它需要创建一个1GB长的内存段。
链接器继续并分配此内存,准备将其放入最终的可执行文件中。
链接器意识到此内存位于.bss
部分并标记为NOBITS
,这意味着数据只是0,并且不需要物理放入最终可执行文件。它避免写出1GB的数据,而只是抛弃分配的内存。
链接器只向编译的代码写入最终的ELF文件,生成一个小的可执行文件。
更智能的链接器可能能够避免上面的步骤2和3,从而使编译时间更快。实际上,像你这样的场景在实践中经常不足以使这样的优化变得有价值。
动态与静态分配
如果你想弄清楚上面的哪一个(动态与静态分配)在你的程序中实际使用,这里有一些想法:
链接器需要使用与最终程序一样多的内存(加上一点)。如果要静态分配4GB的RAM,则链接器需要4GB的RAM。这并不隐含于链接器的工作方式,而只是它们的实现方式。
在分配大量内存时,动态执行此操作可以让您更好地进行错误处理(在屏幕上显示用户友好的消息,说明您没有足够的内存,而不是只是无法加载可执行文件来自操作系统的消息转发给用户);
动态分配内存可让您根据实际需要选择分配多少内存。 (如果data
是缓存,则用户可以选择缓存大小,如果存储中间结果,则可以根据问题对其进行大小调整等。)
动态分配内存可以让你以后释放它,如果你的程序需要继续并在完成内存后做更多的工作。
最后,如果以上几点无关紧要,你可以处理更长的编译时间,那可能并不重要。静态分配内存可以简单得多,而且通常是少量内存或丢弃应用程序的正确方法。