昨天,同事突然来找我,说是编译出来的程序,在 S60 第五版的模拟器上有问题,只能在第一次的时候顺利打开,退出再次进入,则失败。表现的现象有两种,如果是用鼠标在功能表中点击进入,则没有任何提示,如果是用键盘上下键定位图标然后回车,则会报“功能表:系统错误(-1)”。
该同事素日也是以技术过硬著称的,这次也事先做了不少测试,告诉我一个听起来莫名其妙的规律:mmp 文件中有两个 cpp,注释掉就正常,放开就出问题,神奇的地方在于,这两个 cpp 中的类,并没有在任何地方使用到。第一个反应是存在全局对象,经过仔细的代码检查,排除此情况。根据 -1 这个数值,开始怀疑某些有用的文件在第一次程序执行后被无意删除,经过更大量的运行前后比对工作,此情况也排除。
至此,尽管觉得万般不可能,但还是开始怀疑代码问题。首先怀疑所有用到资源的语句(还是那个 -1 闹得,总觉得是有什么东西缺失了),没有解决;接着把所有预编译指令外的代码注释掉,没有解决,再接着把所有头文件包含注释掉,问题消失。由此现象初步推断,与头文件包含有关。于是反过来尝试,把注释掉的头文件逐个放开,结果只包含第一个头文件,问题就又出现了,很振奋,貌似出现了转机。这个头文件是工程代码,里面除了系统头文件的包含,就是我们的类声明。没敢怀疑系统头文件的问题,先把我们自己的代码注释掉,问题依然。不得已开始怀疑系统头文件,一个一个屏蔽、放开地做测试,发现其中四个都会导致问题,其中一个的文件名是 aknsettingpage.h。继续试验后发现,如果两个 cpp 中都包含此文件,并都加入到工程中编译。就会出问题,把其中的一个 cpp 从工程中移除,问题就消失。我们两个大眼瞪小眼,一万个不敢相信。昨天就这么过去了。
今天上午忙了些别的事,下午继续搞。同事又告诉我一个新动向,他把模拟器中的 Free Memory 选项从默认的 48MB 调整为 128MB 之后,情形稍有好转。原来的状况是,一旦第二次进入程序失败,关闭模拟器再重新打开,程序仍然无法进入;把空闲内存调整后有了进步,模拟器重新打开后,程序的第一次运行是可以起来的。我猛然怀疑到问题可能出在模拟器的内存分配和占用上。于是看了一下平时几乎不会注意的 WINSCW 下的可执行文件大小,我的乖乖,67MB!几乎每个 cpp 对应的 .o 文件大小都接近 1MB,连那个仅仅只有一条包含 aknsettingpage.h 头文件语句的 cpp,对应的 .o 竟然也达六百多 KB,我越发坚信自己的怀疑是正确的:由于工程的增大,cpp 文件的个数的增多,大量的调试符号被链接到可执行文件中,使得其体积迅速膨胀,模拟器无法正常加载。而且,我可以据此怀疑提出相对合理的对现象的解释。同事听完后半信半疑,要求我证实。接下来的数次实验(不断调整 Free Memory 的值,观察程序启动的现象),尽管与我的推测不尽吻合,但是也令同事开始更加相信与内存有关。真正让同事也和我完全持有相同观点的事实是:我们重现了之前问题出现与不出现两种场景,把各自的可执行文件做了对比,出现问题的文件大小为 656xxKB,而不出现问题的文件大小为 6550xKB。出于计算机专业人员对 65536 这个数字的高度敏感,终于统一了认识。
由此,我的同事向他的组内发布了两条注意事项:
1、不必要的系统头文件,请不要包含到 cpp 中,这样会引入额外的大量符号
2、如果合理而且可行,建议把若干个 cpp 合并为一个,这样可以有效消除冗余符号
事实上,经过测试,他用 copy *.cpp xxx.cpp 命令把两组 cpp 合并为了两个 cpp,使得可执行文件的体积锐减将近 20MB,成绩喜人。
最后说一个测试过程中的副产品。在 mmp 文件中可以通过 OPTION 语句向编译器指定命令行开关。此 OPTION 语句在之前的官方文档中并未提及,但最新的文档则予以了公开,大家可以参考:http://library.forum.nokia.com/index.jsp?topic=/S60_5th_Edition_Cpp_Developers_Library/GUID-35228542-8C95-4849-A73F-2B4F082F0C44/sdk/doc_source/ToolsAndUtilities94/Build-ref/Mmp-ref/mmpFileStatements.html。我使用 OPTION CW -sym off 语句关闭了 WINSCW 的 UDEB 编译的符号保留,使得 67MB 版本的可执行文件降到了 9MB,另一个 81MB 的版本降为了 12MB。不过,负面作用是,IDE 里的断点再也无法工作了。