再议易语言静态编译重定位数目过多

0.问题再现

数日之前有朋友联系我,说他的软件静态编译后无法正常启动,已经困扰了三天,多方求助,最后没办法了才打电话给我。

据说该软件已经持续开发维护了10多年,用到很多易语言支持库和模块。根据无法启动的现象,联想到我2015年的一篇博客《静态编译的EXE重定位项不能多于65535个》,我怀疑是静态编译时重定位项过多引起的。

1.无谓的惶恐

关于“静态编译的EXE重定位项不能多于65535个”这个问题的来龙去脉,我以前的博客里讲过。
自那以后,很多朋友对65535这个数目的限定表示担忧,认为一旦软件做大之后很可能会突破此极限导致编译失败。

其中以这篇帖子为典型,措辞较为激烈。但是这篇帖子有两个致命的概念错误让我不屑一顾:第一,他把EXE/PE中的重定位项跟OBJ/COFF中某Section内的重定位项弄混了(评论中曾有 友指出这一点,可惜没几个人看得懂),一个在Link之后一个在Link之前,差的不是一般的远,居然还装模做样的弄一个检测EXE/DLL重定位数的程序来糊弄人。第二,他把 拿出来说“微软解决了这个问题”,然而 解决的是“OBJ内Section数目受限”,而非“Section内重定位数目受限”呀,很明显不是同一个问题。

如果要问这篇帖子的价值,我认为无非就是给那些不明真相的群众无故增加了莫名的惶恐心理。

其实没必要惶恐。

前几天看过一个新闻(当然也是旧闻),说40亿年后银河系可能和仙女座星系相撞,届时地球的命运难测。因此而惶恐,就是杞人忧天。

重定位项过多的问题,值得担忧,却不值得惶恐。用易语言开发的软件,大多是中小型软件,做大的恐怕不多,真正导致重定位项爆棚的实例屈指可数就是明证。多年以后真的做大了,遇到问题了,也有具体可行的解决方案,不用担心天塌下来,天塌不下来。

我三年前就说过了:

把函数/子程序分割,提炼出小的函数/子程序,通过这种方式减少重定位项的数目

事实证明这种方案是切实可行的。只是不太具体。后文将提供更加具体的方案。

声明:我不反对更新易语言修正此问题。我早就从易语言公司离职多年,要改也不归我管,我凭什么反对呀。能不能改,要不要改,改的话采用什么方案,都是现任开发者要考虑的问题。我只是从技术上做一点点分析,发表一点个人的看法。

根据我(Liigo)的分析结果,整理出以下表格供大家参考。分析方法详见下文。

1. 变量/常量/立即数

类目 重定位 备注
全局变量 程序集变量 每出现一次,增加一次重定位 如 和
子程序内的局部变量 不增加重定位
类模块内的私有成员和局部变量 不增加重定位
自定义类型成员变量 不增加重定位
文本和字节集立即数 每出现一次,增加一次重定位 如 和
文本和字节集 #常量 每出现一次,增加一次重定位 如 和 和
图片和声音资源常量 每出现一次,增加一次重定位 如 和
数值和逻辑类型的立即数和常量 不增加重定位 包括小数和双精度小数以及各种整数

小结1:局部变量不增加重定位,数值逻辑常量不增加重定位,全局变量和文本字节集常量增加重定位。

注:表中所述“每出现一次”,是指在程序源代码中使用某项目的次数。如 中 出现两次, 出现一次;又如: 中文本立即数 出现三次。

再如下面的循环代码,虽然会循环执行 100 遍,但 在源代码内仅出现一次。

2. 函数/子程序/方法

类目 重定位 备注
调用子程序 不增加重定位 包括跨程序集调用
调用DLL命令 不增加重定位 包括在外部模块定义的DLL
调用类模块方法 不增加重定位
调用外部模块内子程序 不增加重定位
调用外部模块内类方法 不增加重定位
调用支持库内函数 每出现一次,增加一次重定位
调用支持库内数据类型方法 每出现一次,增加一次重定位

小结2:调用支持库定义的函数/方法增加重定位,调用易语言自己定义的子程序/方法/命令不增加重定位。

3. 组件属性

使用组件属性不增加重定位。

叠加效果

上面一行代码至少增加3个重定位:

  • 使用全局变量一次
  • 调用支持库方法一次
  • 使用文本立即数一次

“第一次出现”例外

前面表格中统计的对重定位的增减,都是针对“非第一次出现”而言。“第一次出现”属于例外,可能会导致增加不止一次重定位。

以第一次调用某个模块的子程序为例,虽然该调用本身不增加重定位,但该子程序的函数体代码需要被编译进来,其内部使用的其他所有相关代码也要编译进来,自然要增加多项重定位,这也算是一种叠加效果。

第一次调用某个模块的子程序/方法,可能会一次性引入数十数百甚至成千上万的重定位。具体数目取决于该代码块以及相关代码块内所有叠加效果之和。

第一调用某个支持库函数/方法,仅仅引入极少数重定位。因为支持库内相关的重定位存在于其自身obj内部,独立于易语言编译生成的obj。

总结

增加重定位的情况:

  • 使用全局变量、程序集变量
  • 使用文本和字节集类型的立即数和常量
  • 调用支持库内的函数和方法

不增加重定位的情况:

  • 使用局部变量、类模块私有成员、组件属性
  • 使用非文本非字节集的立即数和常量
  • 调用子程序、调用类模块方法、调用DLL命令
  • 调用支持库函数和方法
  • 调用外部模块的子程序和方法(但可能附带增加大量重定位)

[注意] 隐含增加(大量)重定位的情况:调用外部模块的子程序和方法。

编译器需要优化的地方

下面的代码,对全局变量 连续赋值相同的文本,按说应该等价于一行 ,只要两次重定位就行(使用全局变量和文本立即数各一次),实际上生成了20次重定位。编译器可考虑消除无效代码,减少不必要的重定位。

再比如下面, 后面的代码必然是无效代码,也应在被消除之列:

再比如,ecode段和econst段有许多互相交叉重定位的情况(A重定位至B,B重定位至A),是否可以优化掉p>

分析方法

修改易语言安装目录 子目录内的 文件,打开以下选项:

这样在静态编译之后,我们能够看到易语言编译生成的 .obj 文件,文件名与EXE文件名一致。

3.手工解决办法

随着软件的不断开发,功能越来越多,代码越来越多,重定位也越来越多。大致来说,重定位数量是随代码量不断增长的,是一个线性增长的过程。当重定位数量超越极限数值之后,软件必然无法启动,能很快被软件开发者发现;而此时重定位的数量必然是刚刚越过极限,只要少量的减少重定位就能使其回到极限数量以内。这是我们能够快速有效解决该问题的基本原理。

动手之前先统计哪个项目引入的重定位最多,枪打出头鸟,效果更明显。举个例子,如果发现代码中大量多次使用某个全局变量,或大量多次使用某个支持库函数/方法,那它就是我们要找的那个出头鸟。注意是代码中的使用次数,即上文所述出现次数,而非运行时被执行的次数。

把重复代码变成循环

原理:把“出现N次”变成“出现1次”。

改成循环后执行效果是一样的,但重定位数少了一万倍:

把全局变量变成局部变量

原理:把全局变量“出现N次”,变成局部变量“出现N次” + 全局变量“出现1次”。

原理:使用全局变量增加重定位,而局部变量不增加。

引入一个新的局部变量,记录全局变量的值,然后把所有全局变量变成局部变量,执行结果不变,重定位数从N减小到1:

把全局变量变成子程序

原理:把全局变量“出现N次”,变成在子程序内“出现2次”。

原理:使用全局变量增加重定位,而调用子程序不增加重定位。

新增两个子程序分别读取和设置全局变量的值,然后用到该全局变量的情况,统统改成调用子程序,这样修改之后,执行结果不变,但是重定位数由N减少到2:

缺点:会影响程序运行性能。

把调用支持库函数方法变成调用子程序

原理:调用支持库函数和方法增加重定位,调用内部子程序不增加重定位。

引入一个新的封装子程序,其内部的支持库函数(或方法)调用只需“出现一次”,而封装子程序虽然“出现N次”但不增加重定位。
如此修改后,重定位数目缩减N倍。

缺点:会影响程序运行性能。

把立即数或常量变成局部变量或子程序

参考“把全局变量变成局部变量”“把全局变量变成子程序”。

放弃使用某个模块

原理:模块内部代码可能引入大量重定位,而这些代码不受我们控制。

原理:模块比支持库引入的重定位要多的多。

原理:把EXE内部分代码转移到DLL内,该DLL有另外65535个重定位可用。

在主程序EXE里放弃使用某个模块,把对应的使用该模块的代码移入新的DLL里面,不够的话还可以分出第二第三个DLL,每个DLL都有65535个重定位可用。

4.接受现实

5.皆大欢喜 [增补]

本节内容为 2018/06/29 增补。

其实微软还真的是已经解决了这个问题。现在我蛮后悔在以前的那篇博文中盲目的错误的说了下面这些话:

所以这个问题根本就是无解。归根揭底是C/C++编译链接系统COFF格式OBJ文件结构设计不合理。

这是因为我知识点有欠缺,未能及时意识到其实已经存在现成的解决方案。直到昨天吴总告诉我,我才恍然大悟。

IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000

The section contains extended relocations. The count of relocations for the section exceeds the 16 bits that is reserved for it in the section header. If the NumberOfRelocations field in the section header is 0xffff, the actual relocation count is stored in the VirtualAddress field of the first relocation. It is an error if IMAGE_SCN_LNK_NRELOC_OVFL is set and there are fewer than 0xffff relocations in the section.

https://docs.microsoft.com/zh-cn/windows/desktop/api/winnt/ns-winnt-_image_section_header

这应该是最好的结果。易语言编译部分无需改动,链接部分只需很小的改动,用户的易语言源代码无需改动。升级之后支持几百万上千万的重定位都是小意思。


5.8版相对5.71版更新内容:
1. 解决了静态编译时重定位项数目超过65535个后所编译exe程序启动失败的问题;
2. 为所编译exe程序的运行时错误提供了定位到对应易语言源程序位置的支持;
3. 窗口与其窗口程序集之间现在可以相互跳转。

20221019 LIIGO补记:该版本更新第一项内容(即解除重定位项数目限制),在吴涛吴总委托下,仍由我本人实际执行源代码修改。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年9月15日
下一篇 2022年9月15日

相关推荐