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

我已经阅读了其他关于是否存在性能问题的线程(大多数人说没有),并且您应该始终在接近它们将被使用的地方声明变量。我想知道的是,这种情况是否应该避免,还是更可取。

例子:

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

   cout << someString;
}

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


当前回答

我没有发帖回答JeremyRR的问题(因为他们已经回答了);相反,我只是发表了一个建议。

对于JeremyRR,你可以这样做:

{
  string someString = "testing";   

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

我不知道你是否意识到(我第一次开始编程时没有意识到),括号(只要它们是成对的)可以放在代码中的任何地方,而不仅仅是在“if”、“for”、“while”等后面。

我的代码编译在微软Visual c++ 2010 Express,所以我知道它的工作;此外,我已经尝试使用它在括号外定义的变量,我收到了一个错误,所以我知道变量被“销毁”。

我不知道使用这种方法是否是不好的做法,因为许多未标记的括号会很快使代码无法阅读,但也许一些注释可以澄清这些问题。

其他回答

我没有发帖回答JeremyRR的问题(因为他们已经回答了);相反,我只是发表了一个建议。

对于JeremyRR,你可以这样做:

{
  string someString = "testing";   

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

我不知道你是否意识到(我第一次开始编程时没有意识到),括号(只要它们是成对的)可以放在代码中的任何地方,而不仅仅是在“if”、“for”、“while”等后面。

我的代码编译在微软Visual c++ 2010 Express,所以我知道它的工作;此外,我已经尝试使用它在括号外定义的变量,我收到了一个错误,所以我知道变量被“销毁”。

我不知道使用这种方法是否是不好的做法,因为许多未标记的括号会很快使代码无法阅读,但也许一些注释可以澄清这些问题。

第4.8章K&R的C程序设计语言中的块结构

类中声明并初始化的自动变量 每次输入块时,块都被初始化。

我可能没有在书中看到相关的描述,比如:

类中声明并初始化的自动变量 在输入块之前,只分配一次块。

但一个简单的测试可以证明这个假设:

 #include <stdio.h>                                                                                                    

 int main(int argc, char *argv[]) {                                                                                    
     for (int i = 0; i < 2; i++) {                                                                                     
         for (int j = 0; j < 2; j++) {                                                                                 
             int k;                                                                                                    
             printf("%p\n", &k);                                                                                       
         }                                                                                                             
     }                                                                                                                 
     return 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

因此,除非涉及到分析或计算扩展构造函数,否则保持声明接近其用法应该是默认方法。

一般来说,把它放在很近的地方是一个很好的做法。

在某些情况下,出于性能等考虑,需要将变量从循环中取出。

在您的示例中,程序每次都创建并销毁字符串。一些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。

假设你想避免这些冗余的创建/分配,你可以这样写:

for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

或者你可以把常数提出来

const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

大多数编译器是否意识到该变量已经被声明,而只是跳过这一部分,还是每次都在内存中为它创建一个位置?

它可以重用变量所消耗的空间,还可以从循环中提取不变量。在const char数组(上面)的情况下-该数组可以被拉出。但是,对于对象(例如std::string),每次迭代都必须执行构造函数和析构函数。在std::string的情况下,“空格”包含一个指针,其中包含表示字符的动态分配。所以这个:

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

在每种情况下都需要冗余复制,如果变量位于SSO字符计数的阈值之上(并且SSO由std库实现),则需要动态分配和释放。

这样做:

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

在每次迭代时仍然需要一个字符的物理副本,但这种形式可能会导致一次动态分配,因为您分配了字符串,实现应该看到没有必要调整字符串的支持分配。当然,在本例中您不会这样做(因为已经演示了多个更好的替代方法),但是当字符串或向量的内容发生变化时,您可以考虑这样做。

那么,你该如何处理这些选项(以及更多选项)呢?保持它非常接近默认值—直到您充分了解成本并知道何时应该偏离。

从前(c++ 98之前);以下将中断:

{
    for (int i=0; i<.; ++i) {std::string foo;}
    for (int i=0; i<.; ++i) {std::string foo;}
}

警告我已经声明(foo是好的,因为它的范围在{})。这可能是人们首先认为它不好的原因。但很久以前就不是这样了。

如果你仍然要支持这样一个旧的编译器(有些人是Borland),那么答案是肯定的,一个情况下可以把i的循环,因为不这样做使得它使它“更难”的人把多个循环与相同的变量,虽然老实说编译器仍然会失败,这是所有你想要的,如果有一个问题。

如果你不再需要支持这样一个旧的编译器,变量应该保持在你能得到的最小范围内,这样你不仅可以最小化内存使用;但也使项目更容易理解。这有点像问为什么不让所有变量都是全局变量。同样的论点也适用,但作用域略有变化。