【问题标题】:Is it advised to declare variables inside loops?是否建议在循环内声明变量?
【发布时间】:2022-11-04 23:19:20
【问题描述】:

据我所知,在循环内声明变量的效率低于在外部声明并在循环内修改它。

例子:

std::list<<double> l;
for(int i = 0; i < 100000; ++i)
{
    double a;
    a = 500.0 * i;
    l.append(a);
}

另一个带指针的例子:

std::list<<double *> l;
for(int i = 0; i < 100000; ++i)
{
    double* a;
    *a = 500.0 * i;
    l.append(a);
}

这些示例没有足够的意义,但我只想表明在循环内声明了双精度和指针。

问题是,变量的作用域和循环是一样的,所以当循环进行迭代时,它会破坏变量然后再次声明它吗?或者它只是停留在堆中直到for 循环结束?这样做的效率如何?是不是很浪费资源?

我像在 C++ 中一样对其进行编码。

提前致谢!

【问题讨论】:

  • 它将在堆栈而不是堆上(或者可能只是在寄存器中,根本不在主内存中)并且启用(并且可能没有)优化将生成相同的代码
  • 您的假设(通常)是不正确的。然而,什么不可取的是拆分声明和初始化。
  • 始终您首先考虑的应该是最有意义的,而不是最有效的。如果一个变量在循环内使用并在循环的每次迭代中重新初始化,则在循环内声明它。
  • 我的经验法则:使用尽可能小的范围。如果变量在循环之前或之后由代码使用,则在循环之前定义变量。如果变量只在循环中需要,但它的值需要在循环迭代之间携带(例如,任何迭代中的值取决于它在前一次迭代中的值)然后在循环构造本身中定义它(for (int i = 0; ... 是一个基本示例,其中i 用于控制循环)。如果变量只需要在循环体中,并且它的值不依赖于之前的迭代,那么在循环体中定义它。

标签: c++ pointers variables memory-efficient


【解决方案1】:

在内存使用方面,大多数实现没有区别。通常创建堆栈帧以容纳函数内任何位置声明的所有变量。

但是,如果该类型具有构造函数和/或析构函数,则在每次循环迭代时调用它可能会有额外的开销。如果该类型分配自己的内存(例如std::vector),那么这些分配和释放也将在每次循环迭代时发生,而不是只发生一次。

也就是说,如果不需要超过一次迭代,您仍然应该从在循环内声明变量开始!很容易忘记清除任何以前的值,这会很快导致错误。最好先让它正确,然后再优化。

【讨论】:

  • @KonradRudolph 你是对的,已编辑。
  • 太感谢了!因此,这将取决于声明和删除变量需要付出多少努力
  • @Monetillo - 次要观点:声明和破坏变量。删除是完全不同的:如果你用new 创建一个对象,你用delete 销毁它。
【解决方案2】:

首先

double* a;
*a = 500.0 * i;

是完全错误的。它调用未定义的行为,因为a 没有指向double。不要写这样的代码。不要指望指针会变魔术。指针指向某个地方,这就是他们所做的。要存储double,您需要double,而不仅仅是指针。


不要害怕在循环中引入一个临时的double。一个好的编译器会注意到,无论您是像以前那样编写代码还是代码是这样的,最终结果都绝对没有区别:

std::list<double> l;
for(int i = 0; i < 100000; ++i)
{
    l.append(500.0 * i);
}

因此,我希望编译器为三个版本中的任何一个发出相同的输出。见What exactly is the "as-if" rule?

为了清晰和可读性,您应该编写代码。

您想在每个迭代器中使用相同的a,还是在每次迭代中使用不同的a?这就是决定在循环内部或外部声明a 的原因。您可以引入一个范围以将a 的范围限制为尽可能窄,即使在循环之外声明它也是如此:

 std::list<double> l;
 { 
     double a = 0.0;
     for (int i = 0; i <100000; ++i) {
          l.append(a);
          a += 500.0;
     }
 }

【讨论】:

  • 太感谢了!在指针示例的情况下,它的行为是否相同?
  • @Montlio 哇,实际上我忽略了这个问题。 double* a 只是一个指针,*a 是未定义的行为。您从未分配过可以存储值的double
  • 好吧,我没有写好示例指针,但我想表达的是你有一个循环,在它里面你声明一个指针,用它做一些事情,然后它被存储在某个地方(一个列表或一个向量)。
  • @Monetillo 我明白你想说明什么。这就是我在最初的答案中所指的。但是,一旦您动态分配内存,情况就不同了。我不确定分配内存是否可以优化掉。我记得事情不是这样的
  • @Monetillo 如果您将指针指向在循环外部分配的一些double,那么我希望它与double a; 相同,尽管编译器可能更难看到里面什么都没有循环修改指针指向的值。
【解决方案3】:

作为一般规则,我会说尽可能推迟变量定义。它将帮助您拥有干净的代码和更高效的程序。

但对于循环的情况,情况有点不同。假设我们有两个这样的循环:

// loop 1 - define variable outside the loop
MyType t;
for(auto x : x_container)
{
   t = DoSthAndGetT(x);
   // the rest of the code
}

// loop 2 - define variable inside the loop
for(auto x : x_container)
{
   MyType t(DoSthAndGetT(x));
   // the rest of the code
}

对于循环 1,您需要调用一个构造函数、一个析构函数和 n 个赋值。 对于循环 2,您有 n 个构造函数和 n 个析构函数

因此,考虑到构造函数、析构函数和赋值的繁重,您可以决定选择的方法。

【讨论】:

  • 太感谢了!因此,如果在循环内声明变量时 cpu 工作得更多,那么效率会降低,不是吗?
  • @Monetillo好吧,根据CPU使用情况来判断并不是那么容易,因为您无法理解循环内的哪部分代码使用了cpu。正如我在答案中建议的那样,深入了解您的类型的 ctor、dtor 和赋值运算符,并据此做出决定。
猜你喜欢
  • 1970-01-01
  • 2012-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 2014-06-15
  • 1970-01-01
相关资源
最近更新 更多