原文:http://blogs.msdn.com/xiangfan/archive/2008/09/19/minimize-the-size-of-your-program-high-level.aspx
注:本文的主要目的在于最小化可执行文件的大小。里面提到的技巧并不适用于实际的应用。生成的PE文件是否有效依赖于特定的架构、操作系统和工具
现在,程序的大小已经不再是程序员需要担心的问题了。但是,编写一个非常精简的程序也是一件很有意思的事情。
为了简化分析,本文仅限于讨论WinXP,x86平台上的PE格式程序。这里选择VC9做为我们的编译器。目标程序见文末的清单。
如果你使用默认的编译选项编译这段代码,你将得到一个56320字节的可执行文件。
cl /c test.cpp
link test.obj
已经很小了,是吧?不过实际上,它里面的大部分代码并不是你写的。默认编译器会静态链接到CRT库,所以它们会成为你程序的一部分。我们最好去掉这些“累赘”。让我们给cl.exe加上/MD选项。嗯,test.exe现在只有5632字节了!不过不用着急,现在还只是刚刚开始,我们还能更进一步的减小它的大小。
如果你查看一下生成的文件,你仍然会发现许多不属于你的东西。比如说,文件的入口函数start是编译器“偷偷”加到文件中用来初始化CRT的(你可以在“Microsoft Visual Studio 9.0/VC/crt/src/crt0.c”中查看这个函数的源代码)。在我们的程序中,这些初始化代码可以被忽略。通过/entry:"main"参数,我们可以告诉链接器直接使用我们的main函数作为程序的入口点。不过很不幸,在VC9中如果你这样做的话会得到如下的错误:
MSVCRT.lib(gs_report.obj) : error LNK2019: unresolved external symbol __imp__TerminateProcess@8 referenced in function ___report_gsfailure
MSVCRT.lib(gs_report.obj) : error LNK2019: unresolved external symbol __imp__GetCurrentProcess@0 referenced in function ___report_gsfailure
MSVCRT.lib(gs_report.obj) : error LNK2019: unresolved external symbol __imp__UnhandledExceptionFilter@4 referenced in function ___report_gsfailure
MSVCRT.lib(gs_report.obj) : error LNK2019: unresolved external symbol __imp__SetUnhandledExceptionFilter@4 referenced in function ___report_gsfailure
MSVCRT.lib(gs_report.obj) : error LNK2019: unresolved external symbol __imp__IsDebuggerPresent@0 referenced in function ___report_gsfailure
不用担心,这些是/GS选项导致的,我们之后会讨论。现在我们直接将kernel32.lib传给link.exe来解决这个错误。现在test.exe只有3072字节了。
/GS用于探测潜在的缓冲区溢出的,默认是打开的。这个选项非常有用,不过这里我们的主要目的是优化程序的大小,并不考虑潜在的缓冲区溢出问题,因此只好割爱了。传递/GS-给cl.exe之后test.exe的大小减少到2560字节!
现在程序中已经没有多余的代码了,所以我们可以关注其它的问题。在PE格式中,不同类型的数据被放到不同的段之中。比如,VC会将代码存放到".text"段,而将数据存放到".data"段。默认每个段都要对齐到512字节的边界。这也是为什么我们前面得到的大小都是512的倍数。我们可以将这些段合并来节省空间。下面的选项可以让link.exe合并段:/MERGE:.rdata=.text /MERGE:.data=.text(注:对于实际的应用,使用上述开关可能会导致问题)。这样test.exe就只有1536字节了。
如果使用/O1选项(针对大小优化),我们可以将程序的大小减少到1024字节。如果我们将段对齐的要求放宽到4字节(使用连接器选项/ALIGN:4)(注:对于实际的应用,使用上述开关可能会导致问题),我们将得到896字节的程序。
经过这些宏观的优化之后,我们将exe整整减小了98%!
cl /O1 /MD /GS- /c test.cpp
link test.obj /entry:"main" /MERGE:.rdata=.text /MERGE:.data=.text /ALIGN:4
PS:如果main函数是空函数的话,结合上面的编译选项,能够生成仅有468字节的程序。这是宏观优化能够做到的极限了。如果还要再进一步的话,我们就需要微观的优化了。 微观优化:http://blog.csdn.net/nineforever/archive/2008/10/17/3092825.aspx
程序清单如下:
#include <cstdio>
#include <cstring>
static unsigned char Tbl[9]={1,1,2,3,7,5,6,4,8};
int main()
{
unsigned char BufT[0x401];
int n;
scanf("%d",&n);
for (;n>0;--n) {
scanf("%s",BufT);
unsigned long keyT[4]={0xCBDCEDFE,0x8798A9BA,0x43546576,0x00102132};
unsigned char *Buf=BufT;
int tt;
while (tt=*Buf++) {
{
int t=Tbl[tt%9];
for (int k=0;k<4;++k) keyT[k]=keyT[k]*t+0xFBFC;
}
unsigned long Buf1[4];
int t=tt&0xF;
memcpy(Buf1,(char *)keyT+t,0x10-t);
memcpy((char *)Buf1+0x10-t,keyT,t);
memcpy(keyT,Buf1,16);
}
for (size_t k=0;k<4;++k) printf("%08X",keyT[k]);
printf("/n");
}
return 0;
}