Page 97 - 《软件学报》2025年第7期
P. 97

3018                                                       软件学报  2025  年第  36  卷第  7  期


                    此外, 本节也记录了      O2NMatcher 的源代码函数集合生成时间. 通常, 为一个项目生成所有源代码函数集合所
                 需的时间不超过      10 s. 而且, 源代码函数集合生成过程可以在后台执行, 并且源代码函数集合只需在执行二进制到
                 源代码函数匹配前只需生成一次即可.

                 5.4   特征贡献度分析
                    在第   4.1  节中, O2NMatcher 为内联函数调用预测选择了几个特征. 本节将分析这些特征的贡献, 以揭示
                 ECOCCJ48  是如何决定进行内联操作的.
                    图  11  可视化了  ECOCCJ48  中学习到的部分分类规则, 在被调用者特征中, Static 关键字在前几层的内联调用
                 预测中起着重要作用. 可以看出, 当被调用函数被              Static 关键字定义时, 该函数更倾向于被内联. 这是因为            Static 关
                 键字定义的函数通常只在一个源文件范围内可见, 这意味着它们不会成为链接阶段的负担, 并且它们的内联不会
                 增加其他模块的代码大小. 此外, Static 函数通常包含较简单的逻辑, 因此更适合内联, 以减少函数调用开销并提高
                 执行效率.

                                                               无标签
                                                      被调用函数 Static 关键字数量    0.5
                                                                           -
                                                   不满足                     满足
                                                   标签=内联调用               标签=
                                               被调用函数调用次数    5.5         正常调用
                                                               -
                                           满足                   不满足
                                            标签=            标签=正常调用
                                           内联调用        被调用函数被调用次数    1.5
                                                                        -
                                                     满足                不满足
                                                      标签=            标签=
                                                     内联调用           正常调用
                                             图 11 内联函数调用预测的决策树规则

                    另外, 函数调用图中的函数调用特征在分类内联函数调用时也扮演着重要角色. 例如, “被调用函数被调用次

                 数  ⩽ 1.5”意味着仅被调用一次的函数应被内联. 这条规则反映了这样的事实: 如果一个函数只被调用了一次, 那么
                 内联该函数可能会减少函数调用开销, 从而提高程序的执行速度. 在这种情况下, 内联并不会显著增加代码大小,
                 因此是一个合理的选择.
                                          ⩽ 5.5”表明如果一个被调用函数自身又调用了很少的其他函数                   (即该函数本身不
                    此外, “被调用函数调用次数
                 是复杂的调度者), 那么它更有可能被内联. 这样的函数通常具有较为简单的逻辑, 内联它可以减少函数调用的开
                 销, 而不会显著增加代码的大小. 换句话说, 如果一个被调用函数本身调用了多个其他函数, 那么它可能是一个复
                 杂的调度函数. 这样的函数如果被内联, 可能会导致代码膨胀, 并且不一定会带来显著的性能提升. 因此, 当一个函
                 数只调用了少数几个其他函数时, 它更有可能作为一个功能函数被内联到调用它的函数中去. 这样不仅可以减少
                 函数调用的开销, 还能简化代码逻辑.

                 5.5   结果讨论

                 5.5.1    源代码函数集合的覆盖率分析
                    O2NMatcher 虽然能够生成大量的源代码函数集合以供内联二进制函数比对, 但生成的源代码函数集合仍存
                 在部分缺失. 如图     8  所示, 有  38%  的内联二进制函数找不到生成的源代码函数集合. 这些遗漏主要归因于构建的
                 源代码函数调用流图的不完整性. 在           C/C++中, 预处理器指令被广泛使用, 其中条件编译指令              (如#ifdef、#if、#elif、
                 #else、#endif) 会导致编译区域的可变性      [29] . 程序可能会根据不同的编译环境将不同的源代码部分编译成二进制.
                 覆盖所有可能的编译结果较为困难. 因此, 大多数现有的                 C/C++静态分析器无法确定哪些代码参与了编译. 编译区
                 域的错误识别将导致构建的          FCG  不完整且不准确, 这进一步影响了生成的源代码函数集合的质量.
   92   93   94   95   96   97   98   99   100   101   102