初始化全局变量时,Clang-4.0会生成冗余方法

clang-4.0 generates redundant methods when initializing global variables

本文关键字:冗余 方法 全局变量 Clang-4 初始化      更新时间:2023-10-16

我如今正在学习LLVM,通过观察叮当声如何处理复杂情况。我写了(最高级,而不是在功能中):

int qaq = 666;
int tat = 233;
auto hh = qaq + tat;

我使用命令:

clang-4.0 003.cpp -emit-llvm -S -std=c++11

和clang生成这样的代码:

@qaq = global i32 666, align 4
@tat = global i32 233, align 4
@hh = global i32 0, align 4
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @_GLOBAL__sub_I_003.cpp, i8* null }]
; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init() #0 section ".text.startup" {
  %1 = load i32, i32* @qaq, align 4
  %2 = load i32, i32* @tat, align 4
  %3 = add nsw i32 %1, %2
  store i32 %3, i32* @hh, align 4
  ret void
}
; Function Attrs: noinline uwtable
define internal void @_GLOBAL__sub_I_003.cpp() #0 section ".text.startup" {
  call void @__cxx_global_var_init()
  ret void
}

我与_GLOBAL__sub_I_003.cpp混淆:为什么Clang会生成一个实际上只调用另一个功能的函数(并且不做其他功能)?甚至他们两个都没有参数?

免责声明:这是我对逻辑的解释,我不是LLVM团队的一部分。

为了理解此背后的原因,您必须了解软件工程中的基本概念:复杂性会创建错误,并使测试更加困难。

,但首先,让您的示例更有趣:

int qaq = 666;
int tat = 233;
auto hh = qaq + tat;
auto ii = qaq - tat;

导致:

; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init() #0 section ".text.startup" !dbg !16 {
  %1 = load i32, i32* @qaq, align 4, !dbg !19
  %2 = load i32, i32* @tat, align 4, !dbg !20
  %3 = add nsw i32 %1, %2, !dbg !21
  store i32 %3, i32* @hh, align 4, !dbg !21
  ret void, !dbg !20
}
; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init.1() #0 section ".text.startup" !dbg !22 {
  %1 = load i32, i32* @qaq, align 4, !dbg !23
  %2 = load i32, i32* @tat, align 4, !dbg !24
  %3 = sub nsw i32 %1, %2, !dbg !25
  store i32 %3, i32* @ii, align 4, !dbg !25
  ret void, !dbg !24
}
; Function Attrs: noinline uwtable
define internal void @_GLOBAL__sub_I_example.cpp() #0 section ".text.startup" !dbg !26 {
  call void @__cxx_global_var_init(), !dbg !28
  call void @__cxx_global_var_init.1(), !dbg !29
  ret void
}

因此,我们看到Clang为每个非平凡的初始化发出一个函数,并在_GLOBAL__sub_I_example.cpp()中呼叫每个功能。这是有道理的,而且是明智的,因为这种方式整洁地组织了,否则可能会在更大/更复杂的文件中变成混乱。

请注意,这是您示例中所应用的逻辑的完全相同。

这样做

否则将暗示类型的算法:"如果有一个非平凡的全局初始化,然后将代码直接放入翻译单元的全局构造函数"。

请注意以下内容:

  • 当前的逻辑已经正确处理了该情况。
  • 在优化的代码中,最终结果将完全相同。

那么,这种逻辑真的会给我们带来什么?

  • 更多的分支进行测试。
  • 更多的机会偶然插入错误。
  • 从长远来看可以维护的更多代码。
  • 在非优化构建中某些翻译单元的全局初始化中删除单个功能调用。

以正确的方式保持事情是正确的决定。