C++ 防止意外对象不兼容? TL;博士
在不同的编译单元中控制条件编译的共享(可能是模板化的)头的预处理器指令中,防止因编译器参数输入错误而导致的二进制不兼容 前 基本情况 最近,我遇到了一个奇怪的bug:症状是一个SIGSEGV,在重新编译后似乎总是发生在同一个位置。这让我相信有某种内存损坏正在发生,而实际的底层指针根本不是指针,而是某个数据段 我把你从漫长而艰苦的旅程中解救出来,花了几乎两天的时间来追踪这个问题。可以说,Valgrind、GDB、nm、readelf、电子围栏、GCC的堆栈破坏保护以及其他一些措施/方法/方法都失败了 在彻底的毁灭中,我的注意力转向了构建过程中最好的细节,这类似于:C++ 防止意外对象不兼容? TL;博士,c++,linker,c-preprocessor,symbol-table,misspelling,C++,Linker,C Preprocessor,Symbol Table,Misspelling,在不同的编译单元中控制条件编译的共享(可能是模板化的)头的预处理器指令中,防止因编译器参数输入错误而导致的二进制不兼容 前 基本情况 最近,我遇到了一个奇怪的bug:症状是一个SIGSEGV,在重新编译后似乎总是发生在同一个位置。这让我相信有某种内存损坏正在发生,而实际的底层指针根本不是指针,而是某个数据段 我把你从漫长而艰苦的旅程中解救出来,花了几乎两天的时间来追踪这个问题。可以说,Valgrind、GDB、nm、readelf、电子围栏、GCC的堆栈破坏保护以及其他一些措施/方法/方法都失败
- 建一个小图书馆
- 建造一个大图书馆,使用小图书馆
- 构建大型库的测试套件
- 1:将实现为预处理器符号的选项保存在一些定义良好的位置,最好通过单独的构建步骤提取。提供检查脚本,使用该脚本检查所有编译器定义,并在用户代码中定义。将此检查集成到构建过程中。可能使用Levenshtein距离或类似方法检查拼写错误。昂贵,而且脚本/解决方案可能会变得复杂。类似标志可能存在问题(但为什么有它们?),编译后的库代码必须附带其他文件。(好吧,也许对于矮人2来说,这是不真实的,但让我们假设我们不想要它。)
- 2:集中构建选项:价格便宜,自定义选项保持打开状态(想想makefile.local),但会造成整体畸形,强大的项目耦合
- 条件编译确实在高性能代码中占有一席之地,使用模板和enable_if-s做任何事情都会导致不必要的过度复杂。虽然上述解决方案通常不可取,但也可能在开发过程中出现
- 请假设您无法控制这种情况,假设您有遗留代码,假设您可以尽一切努力强迫自己避免旁敲侧击
- 如果这些都做不到,可以推广到ABI不兼容检测中,尽管这可能会使问题的范围扩大太多
-
- DT_SONAME不适用
- 其中的其他版本方案也不适用——它们旨在保护本身没有故障的软件包
#ifdef YOUR_NORMAL_FLAG
// some code
#elsif YOUR_SPECIAL_FLAG
// some other code
#else
// in case of a typo, this is a compilation error
# error "No flag specified"
#endif
如果过度使用条件编译,这可能会导致大量编译器选项,但是有一些方法可以解决这个问题,比如定义配置文件
flag=normal
flag2=special
可以通过构建脚本进行解析并生成选项,还可以检查拼写错误,或者可以直接从Makefile进行解析。
make
是您的朋友。他们是我的朋友,但不幸的是,我朋友的朋友不一定是我的朋友,最终,在单独的项目中生成文件,彼此默默地憎恨。消除了缺乏信息就是信息的情况,这是非常成功的。这个解决方案演变成了上面的第1个解决方案——我不喜欢这个解决方案引入了额外的标志,这必须引起注意。如果我们涉及到构建系统,为什么不尝试消除引入额外复杂性的需要呢?此外,我忘了提到我希望保持“否定”行为尽可能简单。这与-U for unde不符-编译器没有关于标志实际上是二进制开关的信息,因此如果构建系统没有显式处理,该选项将失去意义->即时错误,这增加了另一层复杂性。
flag=normal
flag2=special