在循环内声明变量,好的做法还是坏的做法?

新手上路,请多包涵

问题 #1: 在循环中声明变量是好做法还是坏做法?

我已经阅读了关于是否存在性能问题的其他线程(大多数人说不),并且您应该始终将变量声明为接近它们将被使用的位置。我想知道是否应该避免这种情况,或者它是否真的是首选。

例子:

 for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

问题#2: 大多数编译器是否意识到变量已经被声明并且只是跳过该部分,或者它实际上每次都在内存中为它创建一个位置?

原文由 JeramyRR 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 842
2 个回答

这是 极好 的做法。

通过在循环内创建变量,您可以确保它们的范围被限制在循环内。它不能在循环之外被引用或调用。

这边走:

  • 如果变量的名称有点“通用”(如“i”),则不会有将其与代码中稍后某处的另一个同名变量混合的风险(也可以使用 -Wshadow 关于 GCC 的警告说明)

  • 编译器知道变量范围仅限于循环内部,因此如果变量在别处被错误地引用,编译器会发出适当的错误消息。

  • 最后但同样重要的是,编译器可以更有效地执行一些专门的优化(最重要的是寄存器分配),因为它知道变量不能在循环之外使用。例如,无需存储结果以供以后重用。

简而言之,你这样做是对的。

但是请注意,变量 _不应该在每个循环之间保留其值_。在这种情况下,您可能需要每次都对其进行初始化。您还可以创建一个更大的块,包含循环,其唯一目的是声明必须在一个循环到另一个循环中保留其值的变量。这通常包括循环计数器本身。

 {
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

对于问题#2:当函数被调用时,变量被分配一次。实际上,从分配的角度来看,它(几乎)与在函数开头声明变量相同。唯一的区别是范围:变量不能在循环之外使用。甚至可能没有分配变量,只是重新使用一些空闲槽(来自其他范围已结束的变量)。

受限和更精确的范围会带来更精确的优化。但更重要的是,它使您的代码更安全,在阅读代码的其他部分时需要担心的状态(即变量)更少。

即使在 if(){...} 块之外也是如此。通常,而不是:

     int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

写起来更安全:

     (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

差异可能看起来很小,尤其是在这样一个小例子上。但在更大的代码库上,它将有所帮助:现在没有风险将一些 result 值从 f1() 传输到 f2() 块。每一个 result 都严格限制在自己的范围内,使其作用更加准确。从审阅者的角度来看,这要好得多,因为他需要担心和跟踪 的远程状态变量 更少。

甚至编译器也会提供更好的帮助:假设将来,在对代码进行一些错误更改后, result 没有正确初始化 f2() 。第二个版本将简单地拒绝工作,在编译时声明一个明确的错误消息(比运行时更好)。第一个版本不会发现任何东西, f1() 的结果将简单地进行第二次测试,对 f2() 的结果感到困惑。

补充资料

开源工具 CppCheck (C/C++ 代码的静态分析工具)提供了一些关于变量最佳范围的极好提示。

回应关于分配的评论:上述规则在 C 中是正确的,但可能不适用于某些 C++ 类。

对于标准类型和结构,变量的大小在编译时是已知的。 C 中没有“构造”之类的东西,因此在调用函数时,变量的空间将简单地分配到堆栈中(没有任何初始化)。这就是为什么在循环内声明变量时成本“零”的原因。

但是,对于 C++ 类,有一个构造函数,我对此知之甚少。我想分配可能不会成为问题,因为编译器应该足够聪明以重用相同的空间,但初始化很可能发生在每次循环迭代中。

原文由 Cyan 发布,翻译遵循 CC BY-SA 4.0 许可协议

下面的两个片段生成相同的程序集。

 // snippet 1
void test() {
   int var;
   while(1) var = 4;
}

// snippet 2
void test() {
    while(1) int var = 4;
}

输出:

 test():
        push    rbp
        mov     rbp, rsp
.L2:
        mov     DWORD PTR [rbp-4], 4
        jmp     .L2

链接: https ://godbolt.org/z/36hsM6Pen

因此,在涉及分析反对或计算广泛的构造函数之前,保持 declation 接近其使用应该是默认方法。

原文由 Muhtasim Ulfat Tanmoy 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题