在vc6 和 vs 2008下 编译 以下代码,不更改任何编译设置(vc6 40k , s2008 7k)。
一、vc6下,Release 模式 编译处理。
1、去掉不必要的 链接库 工程(Project)-->设置(Settings)-->链接(link)属性页-->对象库/模块(object/library modules) 去掉所有的lib。
选择使用 MSVCRT.LIB kernel32.lib user32.lib。 可以忽略不必要的警告,比如 LINK:warning LNK4098: default lib "LIBC " conflicts with use of other libs; use /NODEFAULTLIB:library
2、工程(Project)-->设置(Settings)-->链接(link)属性页-->在Project Options(工程 选项)
下面的编辑框里加上一句: /ALIGN:2的n次方 这样做之后指定了程序不是驱动程序,系统可以设定的最值不同。
按照处理之后,生成程序 /ALIGN:4096 (3k) /ALIGN:16 (1.59k) /ALIGN:128 (1.87k)。
二、在代码中添加相应的编译设置,达到缩小程序体积的目的。
1、无优化
#include <windows.h> int main() { MessageBox(NULL,TEXT("hello!"),TEXT("hi"),MB_OK); return 0; }
2、代码中设置
#include <windows.h> //自定义加载的库 #pragma comment(lib,"kernel32.lib") #pragma comment(lib,"shell32.lib") #pragma comment(lib,"msvcrt.lib") //自定义函数入口 #pragma comment(linker,"/ENTRY:EntryPoint") //自定义对齐方式 #pragma comment (linker,"/ALIGN:512") #pragma comment(linker,"/FILEALIGN:512") // 优化选项 #pragma comment(linker,"/opt:nowin98") #pragma comment(linker,"/opt:ref") //清除从未引用的函数和/或数据 #pragma comment (linker, "/OPT:ICF")//从链接器输出中删除冗余COMDAT // 合并区段 #pragma comment(linker,"/MERGE:.rdata=.data") #pragma comment(linker,"/MERGE:.text=.data") #pragma comment(linker,"/MERGE:.reloc=.data") int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow ) ; void EntryPoint() { ExitProcess(WinMain(GetModuleHandle(NULL), NULL,GetCommandLine(), SW_SHOWNORMAL)); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int iShowCmd ) { MessageBox(NULL,TEXT("hello!"),TEXT("hi"),MB_OK); return 0; }
经过 上述代码优化,可以将 40k的程序 缩小 为 1k 的程序。
三、
最简单的窗口程序:
#include <windows.h> int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,hPrevInstance,LPSTR szCmdLine,int iCmdShow){ MessageBox(NULL,"Hello World","Hello",0); return 0; }
在VC6.0下以Release方式编译,大小为36,864字节(36.0KB)。用CFF Explorer工具查看可执行文件,
可以看出,文件导入了2个动态库,其中user32.dll仅导入了程序中使用的函数MessageBoxA,kernel32.dll导入了38个函数,显然是WinMain中调用的。
2、把WinMain去掉,自己给定程序入口函数,重写代码如下:
#include <windows.h> #pragma comment(linker,"/ENTRY:EntryPoint")//自定义入口函数 void EntryPoint() { MessageBox(NULL,"Hello World","Hello",0); ExitProcess(0); }
重新编译,文件大小为16384字节(16.0KB)。再用CFF Explorer工具查看可执行文件,结果如下图所示。
可以看出,虽然仍导入了2个动态库,但kernel32.dll只导入了一个上述代码中调用的函数ExitProcess。导入函数的减少使文件大小减小了约20KB。
3、在记事本中打开可执行文件,可以发现文件中存在大量的空字符。
用CFF Explorer工具查看文件节表信息。文件的节表信息如下图所示。
可以看出,文件中存在3个节(.text、.rdada、.data),每个节在文件中对齐后的大小和映射到内存后的大小(实际大小)分别由Raw Size和Virtual Size指出。其中.data节实际大小为0x12(18)字节,与"Hello World"和"Hello"这两个字符串的大小(含结束符\0)一致,却占用文件0x1000(4096)字节大小,故文件中存在大量的空字符(数值为0)。
4、是否可以修改节的对齐方式,更改文件的大小呢?
使用链接器/FILEALIGN和/ALIGN选项可以帮我们做到这一点。
其中/FILEALIGN用来指定节在文件中的对齐方式,/ALIGN用来指定节加载到内存后的对齐方式,对齐字节数必须是2的幂。
修改后代码如下:
#include <windows.h> #pragma comment(linker,"/ENTRY:EntryPoint")//自定义入口函数 #pragma comment(linker, "/FILEALIGN:16")//指定节的文件对齐方式 #pragma comment(linker, "/ALIGN:16")//指定节的内存对齐方式 void EntryPoint() { MessageBox(NULL,"Hello World","Hello",0); ExitProcess(0); }
重新编译,文件大小为800字节,程序仍能正常运行。进一步该小/FILEALIGN和/ALIGN的参数,将能编译但无法运行或编译通不过。
5、 还能减小文件的体积吗?是的。
上述说到该文件存在三个节表和相应的三个节,使用链接器/MERGE选项可以把三个节合并,从而进一步压缩文件。
修改后代码如下:
#include <windows.h> #pragma comment(linker,"/ENTRY:EntryPoint")//自定义入口函数 #pragma comment(linker, "/FILEALIGN:16")//指定节的文件对齐方式 #pragma comment(linker, "/ALIGN:16")//指定节的内存对齐方式 #pragma comment(linker, "/SECTION:.text,ERW")//指定节属性 #pragma comment(linker, "/merge:.rdata=.text")//合并节.rdata到.text #pragma comment(linker, "/merge:.data=.text")//合并节.rdata到.text #pragma comment(linker, "/IGNORE:4078")//忽略4078错误 void EntryPoint() { MessageBox(NULL,"Hello World","Hello",0); ExitProcess(0); }
重新编译,文件大小为704字节,相对初始大小(36KB)已有了明显的变化。
Section信息 如下:
6、 另外,程序优化有时也能减小文件体积:
#pragma comment(linker, "/OPT:REF")//清除从未引用的函数和/或数据
#pragma comment(linker, "/OPT:ICF")//从链接器输出中删除冗余COMDAT
除上述方式外,工程中的一些设置也可以很大程度减小文件体积。
结束!