局部变量初始化是否必须?

对于那些做过一些c / c ++维护或增强的人来说,未经初始化的本地人所造成的维护问题(特别是指针)将是显而易见的,但我仍然会看到它们并偶尔听到性能影响作为其理由。

在c中很容易certificate冗余初始化已经过优化:

$ less test.c #include  main() { #ifdef INIT_LOC int a = 33; int b; memset(&b,66,sizeof(b)); #else int a; int b; #endif a = 0; b = 0; printf ("a = %i, b = %i\n", a, b); } $ gcc --version gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125) 

[未优化:]

 $ gcc test.c -S -o no_init.s; gcc test.c -S -D INIT_LOC=1 -o init.s; diff no_in it.s init.s 22a23,28 > movl $33, -4(%ebp) > movl $4, 8(%esp) > movl $66, 4(%esp) > leal -8(%ebp), %eax > movl %eax, (%esp) > call _memset 33a40 > .def _memset; .scl 3; .type 32; .endef 

[优化:]

 $ gcc test.c -O -S -o no_init.s; gcc test.c -O -S -D INIT_LOC=1 -o init.s; diff no_init.s init.s $ 

那么在什么情况下WRT性能是强制变量初始化不是一个好主意?

如果适用,不需要限制c / c ++的答案,但请明确语言/环境(和可重复的证据比推测更受欢迎!)

简短回答:声明变量尽可能接近第一次使用,如果仍然需要,则初始化为“零”。

答案很长:如果在函数开头声明变量,并且在以后不使用它,则应该重新考虑变量的位置作为本地范围。 然后,您通常可以立即为其分配所需的值。

如果必须声明它未初始化,因为它是在条件中分配的,或者通过引用传递并分配给它,将它初始化为null等效值是一个好主意。 如果您在-Wall下编译,编译器有时可以保存您,因为如果您在初始化之前读取变量,它将发出警告。 但是,如果将其传递给函数,则无法向您发出警告。

如果您安全地将其设置为等效的null,那么如果您传递它的函数会覆盖它,则不会造成任何伤害。 但是,如果你传递给它的函数使用了这个值,你几乎可以保证失败一个断言(如果你有一个),或者至少在第二个断言你使用一个空对象。 随机初始化可以做各种不好的事情,包括“工作”。

如果您认为初始化是多余的,那就是。 我的目标是编写尽可能人性化的代码。 不必要的初始化会使未来的读者感到困惑。

C编译器在捕获单元化变量的使用方面非常擅长,因此现在的危险性很小。

不要忘记,通过“假”初始化,你交易一个危险 – 崩溃使用垃圾(这导致一个很容易找到和修复的错误)在另一个 – 程序采取基于假值的错误行动(导致一个很难找到的bug)。 选择取决于应用程序。 对某些人来说,永远不会崩溃至关重要。 对于大多数人来说,最好尽快捕获错误。

这是一个很好的例子, 过早优化是万恶之源

完整的报价是:

毫无疑问,效率的严重性会导致滥用。 程序员浪费了大量时间来思考或担心程序中非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。 然而,我们不应该放弃那个至关重要的3%的机会。 一个优秀的程序员不会因为这样的推理而陷入自满状态,他会明智地仔细研究关键代码; 但只有在识别出代码之后。

这来自唐纳德克努特 。 你会相信谁……你的同事还是Knuth?
我知道我的钱在哪里……

回到最初的问题:“我们应该进行MANDATE初始化吗?”
我会这样说:

变量应该初始化,除非可以certificate通过不初始化可以实现显着的性能增益。 来自硬数字…

我不确定是否有必要“强制要求”,但我个人认为初始化变量总是更好。 如果应用程序的目的是尽可能紧密,那么C / C ++就是为此而开放的。 但是,我认为我们中的许多人已经烧了一两次没有初始化变量并假设它包含一个有效值(例如指针),而实际上并没有。 地址为零的指针比在该特定位置的最后一个存储器内容中具有随机垃圾更容易检查。 我认为在大多数情况下,它不再是性能问题,而是清晰度和安全性问题。

它应该强制性的。 其原因与性能无关,而是使用单位化变量的危险。 但是,有些情况下它看起来很荒谬。 例如,我看到:

 struct stat s; s.st_dev = -1; s.st_ino = -1; s.st_mode = S_IRWXU; s.st_nlink = 0; s.st_size = 0; // etc... s.st_st_ctime = -1; if(stat(path, &s) != 0) { // handle error return; } 

跆拳道???

请注意,我们正在立即处理错误,因此毫无疑问,如果统计失败会发生什么。

让我告诉你一个关于我在1992年和之后工作的产品的故事,为了这个故事的目的,我们将调用Stackrobat。 我被分配了一个错误导致应用程序在Mac上崩溃,但在Windows上没有,哦,这个错误无法可靠地重现。 质量保证花了一个星期的时间来提出一个可能有十分之一的食谱。

由于实际崩溃发生在执行它的动作之后很好,因此它追踪了根本原因。

最后,我通过为编译器编写自定义代码分析器来跟踪它。 编译器非常乐意为全局prof_begin()和prof_end()函数注入调用,您可以自由地实现它们。 我写了一个从堆栈中获取返回地址的分析器,找到堆栈帧创建指令,将堆栈中的块定位为代表函数的本地,并用一层美味的垃圾涂上它们,如果有的话会导致总线错误元素被解除引用。

这有点像初始化之前使用的六个指针错误,包括我正在寻找的错误。

发生的事情是,大多数时候,如果他们被解除引用,那么堆栈恰好具有显然是良性的价值。 其他时候,这些值会导致应用程序霰弹枪自己的堆,稍后取出应用程序。

我花了两周多的时间试图找到这个bug。

课程:初始化您的本地人。 如果有人抨击您的表现,请向他们展示此评论并告诉他们您宁愿花两周时间运行分析代码并修复瓶颈,而不是必须追踪这样的错误。 调试工具和堆检查器已经变得更好了,因为我必须这样做,但坦率地说,他们更好地弥补了这样的不良做法的错误。

除非你在一个小系统(嵌入式等)上运行,否则本地人的初始化应该几乎是免费的。 MOVE / LOAD指令非常非常快。 首先将代码编写为可靠且可维护的。 重构它是第二个性能。

有时您需要一个变量作为占位符(例如使用ftime函数),因此在调用初始化函数之前初始化它们没有意义。

然而,在我看来,讽刺你知道陷阱的事实并不是坏事。

 uninitialized time_t t; time( &t ); 

这仅适用于C ++,但两种方法之间存在明确的区别。 假设您有一个MyStuff类,并且您想要通过另一个类初始化它。 你可以这样做:

 // Initialize MyStuff instance y // ... MyStuff x = y; // ... 

这实际上做的是调用x的复制构造函数。 它与以下相同:

 MyStuff x(y); 

这与此代码不同:

 MyStuff x; // This calls the MyStuff default constructor. x = y; // This calls the MyStuff assignment operator. 

当然,在复制构造与默认构造+分配时,会调用完全不同的代码。 此外,对复制构造函数的单个调用可能比构造后的赋值更有效。

性能? 如今? 也许当CPU以10mhz运行时它确实有意义,但今天它几乎不成问题。 始终初始化它们。

始终至少将局部变量初始化为零。 如你所见,它没有真正的表现。

 int i = 0; struct myStruct m = {0}; 

你基本上是添加1或2个汇编指令,如果是这样的话。 实际上,许多C运行时都会在“Release”版本中为您执行此操作,并且您不会更改任何内容。

但是你应该把它搞定,因为你现在可以得到这种保证。

不初始化的一个原因与调试有关。 一些运行时,例如。 MS CRT将使用您可以识别的预定和记录的模式初始化内存。 因此,当您通过内存填充时,您可以看到内存确实未初始化且尚未使用和重置。 这对调试很有帮助。 但那是在调试期间。

在C / C ++中,我完全同意你的观点。

在Perl中,当我创建变量时,它会自动置于默认值。

 my ($val1, $val2, $val3, $val4); print $val1, "\n"; print $val1 + 1, "\n"; print $val2 + 2, "\n"; print $val3 = $val3 . 'Hello, SO!', "\n"; print ++$val4 +4, "\n"; 

它们最初都设置为undef。 Undef是假值,占位符。 由于动态类型,如果我添加一个数字,它假定我的变量是一个数字,并用eqivilent false值0替换undef。如果我做字符串操作,字符串的错误版本是一个空字符串,这得到自动替换。

 [jeremy@localhost Code]$ ./undef.pl 1 2 Hello, SO! 5 

所以对于Perl至少要提前声明并且不要担心。 特别是大多数程序都有很多变数。 您使用较少的线条,并且在没有明确初始化的情

  my($x, $y, $z); 

🙂

  my $x = 0; my $y = 0; my $z = 0; 

是的: 总是初始化变量,除非你有充分的理由不这样做。 如果我的代码不需要特定的初始值,我通常会将一个变量初始化为一个值,如果后面的代码被破坏,该值将保证一个明显的错误。

正如你所展示的那样,它不会产生任何影响。 编译器将(在优化版本中)检测是否写入局部变量而不读取和删除代码,除非它有其他副作用。

这就是说:如果你用简单的语句初始化东西只是为了确保它已经初始化了,这样做很好。我个人不这样做,原因只有一个:

它欺骗那些可能在以后维护您的代码的人认为需要初始化。 那个小foo = 0; 会增加代码复杂性。 除此之外,这只是一个品味问题。

如果您通过复杂语句初始化变量,则可能会产生副作用。

例如:

  float x = sqrt(0); 

如果幸运的话,可以通过编译器对其进行优化,并使用聪明的编译器。 使用一个不那么聪明的编译器,它也可能导致代价高昂且无法使用的函数调用,因为sqrt可以 – 作为副作用 – 设置errno变量。

如果你调用自己定义的函数,我最好的选择是,编译器总是假设它们可能有副作用而不是优化它们。 如果函数恰好位于同一个翻译单元中,或者您打开了整个程序优化,则可能会有所不同。

有时变量用于“收集”较长的嵌套ifs / elses块的结果……在这些情况下,我有时会保持变量未初始化,因为它应该稍后由其中一个条件分支初始化。

诀窍是:如果我首先保持未初始化,然后在长if / else块中有一个错误,所以变量永远不会分配,我可以看到Valgrind中的错误:-)当然需要频繁运行代码(理想情况下,定期测试)通过Valgrind。

举个简单的例子,你可以确定它将被初始化为什么(C / C ++)?

 bool myVar; 

我们在一个产品中遇到了一个问题,有时会在屏幕上绘制图像,有时则不会,通常取决于它是用什么机器构建的。 事实certificate,在我的机器上它被初始化为假,并且在同事机器上它被初始化为真。

我认为在大多数情况下使用默认值初始化变量是一个坏主意,因为它只是隐藏了很容易找到未初始化变量的错误。 如果您忘记获取并设置实际值,或意外删除获取代码,您可能从未注意到它,因为0在许多情况下是合理的值。 通常,使用值>> 0触发这些错误要容易得多。

例如:

 void func(int n) { int i = 0; ... // Many lines of code for (;i < n; i++) do_something(i); 

一段时间后,你将添加一些其他的东西。

 void func(int n) { int i = 0; for (i = 0; i < 3; i++) do_something_else(i); ... // Many lines of code for (;i < n; i++) do_something(i); 

现在你的第二个循环不会以0开头,而是使用3,取决于函数的function,它很难找到,甚至还有一个bug。

只是次要观察。 初始化仅在原始类型上或在由const函数指定时易于优化。

a = foo();

a = foo2();

无法轻松优化,因为foo可能有副作用。

在时间之前堆积分配也可能导致巨大的性能命中。 拿一个像这样的代码

 void foo(int x) 

{

ClassA * instance = new ClassA();

// …做一些与“实例”无关的事情… if(x> 5){

 delete instance; return; 

}

// ..做一些使用实例的东西

}

在这种情况下,只需在您将使用它时声明实例,并仅在那里初始化它。 并且编译器无法为您优化,因为构造函数可能具有代码重新排序会发生更改的副作用。

编辑:我无法使用代码列表function:P