20180629 Liigo 补记:此问题已得到完美解决,详见本文最后一节《皆大欢喜》。

0.问题再现

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

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

根据我提供的线索,作者很快找到了易语言论坛的这篇帖子,很快就解决了问题。

1.无谓的惶恐

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

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

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

其实没必要惶恐。

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

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

我三年前就说过了:

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

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

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

2.重定位的来源

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

1. 变量/常量/立即数

类目 重定位 备注
全局变量 程序集变量 每出现一次,增加一次重定位 g1 = 1f(g1)
子程序内的局部变量 不增加重定位
类模块内的私有成员和局部变量 不增加重定位
自定义类型成员变量 不增加重定位
文本和字节集立即数 每出现一次,增加一次重定位 a = "liigo"b = {1,2,3}
文本和字节集 #常量 每出现一次,增加一次重定位 a = #换行符a = #长文本常量b = {}
图片和声音资源常量 每出现一次,增加一次重定位 b = #图片1b = #声音1
数值和逻辑类型的立即数和常量 不增加重定位 包括小数和双精度小数以及各种整数

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

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

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

.计次循环首 (100, )f ()
.计次循环尾 ()

2. 函数/子程序/方法

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

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

3. 组件属性

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

叠加效果

全局变量.方法("参数")

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

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

“第一次出现”例外

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

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

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

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

总结

增加重定位的情况:

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

不增加重定位的情况:

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

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

编译器需要优化的地方

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

a = ""
a = ""
a = ""
a = ""
a = ""
a = ""
a = ""
a = ""
a = ""
a = ""

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

返回 ()
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )

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

分析方法

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

retain_intermediate_files=yes
;  retain_intermediate_files用于设置是否保留链接期间生成的中间文件(比如 obj,res,lib 等文件)。
;  可以设置为 yes 或 no。默认值为no,即不保留中间文件。

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

利用工具 dumpbin.exe(来自Visual Studio安装目录) 查看 obj 文件结构详情,将结果导出到文本文件:

dumpbin /all e.obj > obj.txt

打开文本文件 obj.txt 搜索 "number of relocations" 即可得知各Section内的重定位项数目,注意该数值以十六进制显示。

SECTION HEADER #8.text name0 physical address0 virtual address769C5F size of raw dataB6E7C file pointer to raw data (000B6E7C to 00820ADA)B166 file pointer to relocation table0 file pointer to line numbers1BD number of relocations       <<==== 重定位项看这里0 number of line numbers
60300020 flagsCode4 byte alignExecute ReadRAW DATA #800000000: 5E 6A 00 4B 75 FB FF E6 55 8B EC 81 EC 04 00 00  ^j.Ku???U.ì.ì...00000010: 00 89 65 FC 68 00 00 00 00 B8 00 00 00 00 E8 2A  ..eüh....?....è*......

3.手工解决办法

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

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

把重复代码变成循环

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

信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
信息框 (“测试一下,这个东西能不能编译。”, 0, )
此处省略代码一万行...

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

.计次循环首 (10006, )信息框 (“测试一下,这个东西能不能编译。”, 0, )
.计次循环尾 ()

把全局变量变成局部变量

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

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

信息框 (全局变量, 0, )
信息框 (全局变量, 0, )
信息框 (全局变量, 0, )
此处省略代码一万行...

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

局部变量 = 全局变量
信息框 (局部变量, 0, )
信息框 (局部变量, 0, )
信息框 (局部变量, 0, )
此处省略代码一万行...

把全局变量变成子程序

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

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

全局变量 = 1
信息框 (全局变量, 0, )
全局变量 = 2
信息框 (全局变量, 0, )
全局变量 = 3
信息框 (全局变量, 0, )
此处省略代码一万行...

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

置全局变量 (1)
信息框 (取全局变量 (), 0, )
置全局变量 (2)
信息框 (取全局变量 (), 0, )
置全局变量 (3)
信息框 (取全局变量 (), 0, )
此处省略代码一万行....子程序 置全局变量
.参数 参数
全局变量 = 参数.子程序 取全局变量
返回 (全局变量)

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

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

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

信息框 (“信息1”, 0, )
信息框 (“信息2”, 0, )
信息框 (信息变量, 0, )
信息框 (“信息x”, 0, )
信息框 (“信息y”, 0, )
信息框 (“信息信息zz”, 0, )
此处省略代码一万行...

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

自定义信息框 (“信息1”, 0, )
自定义信息框 (“信息2”, 0, )
自定义信息框 (信息变量, 0, )
自定义信息框 (“信息x”, 0, )
自定义信息框 (“信息y”, 0, )
自定义信息框 (“信息信息zz”, 0, )
此处省略代码一万行....子程序 自定义信息框
.参数 信息文本, 文本型
.参数 按钮, 整数型, 可空
.参数 窗口标题, 文本型, 可空信息框 (信息文本, 按钮, 窗口标题, )

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

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

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

放弃使用某个模块

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

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

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

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

4.接受现实

对绝大多数易语言代码而言,65535个重定位数目足够使用。平时犯不着为其不够用而担心。万一以后真的不够用了,还有的是办法解决。本文就为普通易语言开发者提供了具体切实可行的手工解决方案,操作上并无难度,适用于所有开发者。总之这个事儿根本就不叫事儿,该吃吃,该喝喝,别钻牛角尖。

5.皆大欢喜 [增补]

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

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

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

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

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

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


20181106 LIIGO 补记:2018年6月29日,易语言发布5.8版,解决了本文所述问题,具体更新内容如下:

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

这也意味着本文绝大多数内容已经没有多大意义啦。

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

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

  1. 易语言静态编译链接器大全(为EIDE助手准备滴)

    点击阅读原文 本文中的各个链接器收集于网络,汇总在一起,方便广大易友下载使用: 易语言的大部分支持库是 使用 vc6 编译, 所以, 推荐大家使用 vc6的链接器: 如果使用 其他链接库编译出现问题, ...

  2. 易语言静态连接器提取_易语言静态编译链接器切换工具

    使用说明 将exe程序和"链接器目录配置.ini"文件,复制到易语言安装目录的"tools"文件夹下 并且对"链接器目录配置.ini"进行修 ...

  3. 易语言linux静态编译失败,易语言静态编译出现错误求解决

    静态编译程序的时候出现这个... 开始静态链接... LIBC.lib(crt0dat.obj) : error LNK2005: __cinit already defined in libcmt. ...

  4. 易语言MySQL静态编译,我说易语言静态编译太强大……

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 编译后的文件在virscan有14款软件误报 静态编译后我和我的小伙伴惊呆了-- NOD32你别逗我,玷污了这2%,求大神NOD32的报毒信息是怎么回事 ...

  5. 将易语言程序编译为exe程序

    上一篇文章中讲到如何利用以语言实现简单的人机交互,这篇文章中将会分享如何将易语言程序编译为exe程序. 第一步 编写一个易语言程序 步骤详见上一篇博客,在这里就不再赘述了.易语言实现最简单的人机交互_ ...

  6. 易语言c编译,易语言制作计算软件简单步骤

    相对C++.C#等语言来说,易语言是比较容易学习的,很多做工程或者涉及到计算的工作,日常的计算经常要按计算器,还要看公式,显得极为不便,今天就来教大家怎么编写一个便捷的计算软件,来解决我们日常工作生活 ...

  7. 易语言可以编译c语言,刷屏软件?其实易语言也可以做这种软件

    思路+原理+效果图: 思路:模拟复制粘贴字符串(文本),然后模拟发送,来实现循环发送的功能. 原理:利用易语言的"置剪辑板文本"."模拟鼠标点击"和" ...

  8. 易语言c编译程序集,植物大战僵尸。易语言.版本 2.程序集 窗口程序集1.程序集...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 植物大战僵尸.易语言 .版本 2 .程序集 窗口程序集1 .程序集变量 a, 整数型 .程序集变量 b, 整数型 .程序集变量 c, 整数型 .程序集变量 ...

  9. 易语言编译和c语言,易语言独立编译的EXE文件问题

    2005-08-15 我下了一个游戏软件,我想把它附带的文件删了,不知行不行,因为那些附带的太占空间了,我把删了会不会造成游戏不能正常运行.帮帮忙,谢谢了. 这要看你下载的游戏软件是什么格式的文件,如 ...

最新文章

  1. 不忘历史才能开辟未来,善于继承才能善于创新
  2. Python实现不规则txt文本数据读取并转换为csv文本
  3. Java受查异常和运行时异常的理解
  4. JavaScript——易班优课YOOC课群在线测试自动答题解决方案(八)功能面板
  5. [转载]在VirtualBox中收缩虚拟磁盘映像文件
  6. angular4更改表单中显示的值_angular4 Form表单相关
  7. 和合符放枕头下的作用_深度解析:记忆棉枕头好,还是乳胶枕头好?
  8. 腾讯自研分布式远程Shuffle服务Firestorm正式开源
  9. codevs 1143 纪念品分组
  10. 利用Adobe AIR本地扩展支持Android开发
  11. HDU - 5875 Function [单调性剪枝+预处理]
  12. 第一个servlet
  13. 本地数据库数据导入linux
  14. 操作系统死锁 四个必要条件
  15. 怎样在微信、手机中浏览查看3D模型
  16. MATLAB----符号微积分
  17. # linux下openssl版本问题 /lib64/libcrypto.so.10: version `OPENSSL_1.0.2‘ not found
  18. Idea集成单元测试JUnit
  19. 【虹科案例】极高的精度水平——虹科数字化仪在大型强子对撞机机器保护系统中的应用
  20. 纯JS打造移动端触屏滑动图片集

热门文章

  1. 服务器部署邮件功能_真正连续部署的功能标志
  2. C文件访问 introduce
  3. 三星自定义状态栏_极简操作无需root隐藏S8导航栏和状态栏
  4. java工程师面试常见问题_JAVA软件工程师面试遇到的十个问题,这些你都知道吗?...
  5. 安盎顺汲称叫蒲惺勤狙陡邮可王大胖,从小就和他这个堂弟要好,听了此事,当然不肯罢休,找上
  6. 贴息政策打出“组合拳”,院校实验室建设攻略来了(二)!
  7. BZOJ4833: [Lydsy1704月赛]最小公倍佩尔数
  8. Mathematica
  9. mysql可重复读和幻读的理解
  10. C Primer Plus 第3章 数据和C-编程练习