“在C和C ++的低级效率传统中, 除非你这样做,否则通常不需要编译器来初始化变量 它明确地(例如,局部变量,被遗忘的成员省略 构造函数初始化程序列表)“
我一直想知道为什么编译器不会初始化像int32这样的原语并浮点数为0.如果编译器初始化它会有什么性能影响?它应该比不正确的代码更好。
答案 0 :(得分:3)
实际上,这个论点是不完整的。单元化变量可能有两个原因:效率和缺乏合适的默认值。
1)效率
这主要是过去的遗留问题,当时C编译器只是C语言的汇编语言,并且没有进行任何优化。
现在我们有智能编译器和Dead Store Elimination,在大多数情况下会消除冗余存储。演示:
int foo(int a) {
int r = 0;
r = a + 3;
return r;
}
转化为:
define i32 @foo(i32 %a) nounwind uwtable readnone {
%1 = add nsw i32 %a, 3
ret i32 %1
}
但是,有些情况下,即使是更智能的编译器也无法消除冗余存储,这可能会产生影响。对于稍后初始化的大型数组...编译器可能没有意识到所有值都将最终被初始化,因此不会删除冗余写入:
int foo(int a) {
int* r = new int[10]();
for (unsigned i = 0; i <= a; ++i) {
r[i] = i;
}
return r[a % 2];
}
请注意以下对memset
的调用(我通过使用new
()
调用后缀来获取值,这是值初始化)。即使不需要0
,也没有消除它。
define i32 @_Z3fooi(i32 %a) uwtable {
%1 = tail call noalias i8* @_Znam(i64 40)
%2 = bitcast i8* %1 to i32*
tail call void @llvm.memset.p0i8.i64(i8* %1, i8 0, i64 40, i32 4, i1 false)
br label %3
; <label>:3 ; preds = %3, %0
%i.01 = phi i32 [ 0, %0 ], [ %6, %3 ]
%4 = zext i32 %i.01 to i64
%5 = getelementptr inbounds i32* %2, i64 %4
store i32 %i.01, i32* %5, align 4, !tbaa !0
%6 = add i32 %i.01, 1
%7 = icmp ugt i32 %6, %a
br i1 %7, label %8, label %3
; <label>:8 ; preds = %3
%9 = srem i32 %a, 2
%10 = sext i32 %9 to i64
%11 = getelementptr inbounds i32* %2, i64 %10
%12 = load i32* %11, align 4, !tbaa !0
ret i32 %12
}
2)默认?
另一个问题是缺乏合适的价值。虽然float
可以完美地初始化为NaN
,但整数是什么?没有表示缺少值的整数值,根本没有值! 0
是一个候选人(其中包括),但有人可能认为它是最差的候选人之一:这是一个非常可能的数字,因此可能对手头的用例有特定的意义;你确定你觉得这个意思是默认的吗?
思考的食物
最后,单位化变量有一个简洁的优点:它们是可检测的。编译器可能会发出警告(如果它足够聪明), Valgrind 将引发错误。这会使逻辑问题可检测,并且只能检测到检测到的内容。
当然,诸如NaN
之类的标记值也同样有用。不幸的是......没有整数。
答案 1 :(得分:2)
初始化可能有两种方式影响性能。
首先,初始化变量需要时间。当然,对于单个变量,它可能是微不足道的,但正如其他人所建议的那样,它可以加上大量的变量,数组等。
第二,谁说零是合理的违约?对于零是有用默认值的每个变量,可能还有另一个变量。在这种情况下,如果初始化为零,则会产生进一步的开销,将变量重新初始化为您实际想要的任何值。您基本上支付初始化开销两次,而不是默认初始化没有发生一次。请注意,无论您选择什么作为默认值,零或其他,都是如此。
鉴于存在开销,通常更高效的是不初始化并让编译器捕获对未初始化变量的任何引用。
答案 2 :(得分:0)
基本上,变量引用内存中的一个位置,可以对其进行修改以保存数据。对于一个整数变量,程序需要知道的就是这个地方所在的位置,并且编译器通常会提前计算出来,因此不需要任何指令。但是当你想要它被初始化(比如0)时,程序需要使用额外的指令来执行此操作。
一个想法可能是在程序启动时将整个堆清零,使用memset,然后初始化所有静态内容,但这对于在它之前动态设置的任何内容都不是必需的。读。对于基于堆栈的函数而言,这也是一个问题,每次调用函数时都需要将其堆栈帧清零。简而言之,允许变量默认为未定义的效率要高得多,特别是当堆栈经常被新调用的函数覆盖时。
答案 3 :(得分:0)
使用-Wmaybe -ininitialized编译并找出答案。这些是编译器无法选择原始初始化的唯一地方。
至于堆...