编译器会优化这个for循环吗? 在C++或C++中,如果编译器遇到了< >,其中计数器从 0 > n>代码>,而 n>代码>是变量(不是函数调用,并且不是常数< /强>),编译器是否通过检查变量 n< /代码>(绑定变量)来优化循环吗?将在循环期间更改(为写入而访问,例如:n可以是在循环之前计算的字符串长度),这里的优化是指将其值复制到寄存器以避免内存访问

编译器会优化这个for循环吗? 在C++或C++中,如果编译器遇到了< >,其中计数器从 0 > n>代码>,而 n>代码>是变量(不是函数调用,并且不是常数< /强>),编译器是否通过检查变量 n< /代码>(绑定变量)来优化循环吗?将在循环期间更改(为写入而访问,例如:n可以是在循环之前计算的字符串长度),这里的优化是指将其值复制到寄存器以避免内存访问,c++,c,gcc,optimization,C++,C,Gcc,Optimization,以下是一个例子: for (int c = 0; c < n; c++) { // do something not related to n } for(int c=0;c

以下是一个例子:

for (int c = 0; c < n; c++) {
    // do something not related to n
}
for(int c=0;c

编译器会注意到这一点并对其进行优化吗

是的,至少GCC和大多数其他编译器会这样做。如果您试图查看for循环变量是什么,当您使用-O1或更高版本编译时,调试器会说该变量已优化。当然,编译器不需要这样做。这就是标准所说的

for声明

for (for-init-statement; conditionopt; expressionopt) statement
相当于

{
    for-init-statement
    while (condition) {
        statement
        expression
    }
}
c/c++编译器是否会通过检查变量n(绑定变量)在循环过程中是否会更改来优化循环

“仿佛”规则允许编译器重新排序或重新编写代码,只要可观察的效果是“仿佛”它执行了您编写的代码

这里的关键词是“可观察”。除非可以证明
n
在循环体的执行过程中不能更改,否则循环必须为每次迭代重新计算
c

如果编译器可以访问循环中的所有代码(例如,它是在同一个编译单元中定义的,或者全局优化在链接过程中有另一个外观,
n
从不被引用或指针混淆),那么它很可能能够证明
n
没有变化


在这种情况下,看到循环以某种方式得到优化,您不应感到惊讶。

结果取决于使用的编译器

编译器可以为n使用处理器寄存器,您仍然可以在循环中修改n。所以最小优化是可能的

如果有指向n的指针,并且使用指针间接更改n的值,则将变量的值放入处理器寄存器可能会导致“混叠”问题

例如:

int n = 4;
int *nptr = &n;
for(int i = 0; i < n; ++i)
  --*nptr;
int n=4;
int*nptr=&n;
对于(int i=0;i
编译器必须知道nptr是n的别名,因此每次访问时都必须从内存中读取n,但在许多情况下,编译器根本没有机会知道n和nptr之间的关系


您可以使用volatile关键字阻止编译器优化变量(即
volatile int n=4;

您应该尝试编译并自己查看。编译器中的优化取决于几件事

无论如何,为了回答你的问题,我唯一能做的就是向你提供一个与你的问题最为相似的具体案例

代码很简单:

#include <string>

int main(int argc, char *argv[]) {
  std::string str = "this_is_a_string";
  int size = str.size();
  for (int i = 0; i < size; ++i) {
    str += std::to_string(i);
  }
  return 0;
}
#包括
int main(int argc,char*argv[]){
std::string str=“这是一个字符串”;
int size=str.size();
对于(int i=0;i
结果汇编代码为(对于不同的优化级别):

GCC 6.2-O0
movl$0x0,-0x14(%rbp)//int i=0;
mov-0x14(%rbp),%eax//将i加载到寄存器中
cmp-0x18(%rbp),%eax//加载大小并与寄存器中的i进行比较
jge 401317//如果>=
GCC 6.2-O1
//初始化已启动
添加$0x1,%ebx//++i(现在i存储在寄存器中)
cmp%ebx,-0x5c(%ebp)//比较i和大小(从内存加载)
je 0x80488a3//如果=(而不是>=)则跳转
GCC 6.2-氧气 相同的
-O1


已使用的代码,以及程序集。

请尝试。(填充…)也取决于编译器,我想。到这里看看会发生什么:尝试使用
-O0
-O1
编译代码,或者
-O2
并查看GCC如何使用这些选项中的每一个选项也取决于您在for循环中所做的工作,如果它没有明显的副作用,它可以优化整个过程。它必须确实确保这样的变量没有改变;如果
n
既不是局部变量,也不是内部链接变量(例如,它是全局或对象字段),并且循环体调用函数(特别是在其他TU中定义的函数),那么编译器执行此类优化(可能使用LTCG)是极其困难的。这就是为什么通常在这些情况下,您希望确保循环限制变量是局部变量的原因。这是一个非常危险的假设。这取决于太多的因素,包括循环中的代码或目标体系结构。一般来说,这是错误的。这完全是错误的。允许编译器将
n
存储在寄存器中,前提是它可以证明它在其他地方没有别名(即,没有指向相同内存或某物的指针),但在一般情况下,这很难证明,特别是如果
n
是全局或文件范围的,或者如果您曾经在某个地方传递过指向
n
的指针。此外,系统上只有这么多寄存器,编译器可能必须在循环体中使用它们。尤其是x86-32系统,它有少量的寄存器,以及许多对它们进行破坏的指令。在这里,你不能假设“是”,在我检查的情况下,编译器对寄存器进行了优化,答案的其余部分解释说不需要优化(所有优化都是如此)。我可以生成一个不会将其放入寄存器的情况。由于问题仅将循环体描述为
//执行与n
无关的操作,因此答案是“有时可能”,而不是“是”。后面你说编译器不必做的那部分我
 movl   $0x0,-0x14(%rbp)    // int i = 0;
 mov    -0x14(%rbp),%eax    // load i into the register
 cmp    -0x18(%rbp),%eax    // load size and compare with i in the register
 jge    401317 <main+0x91>  // jump if >=
 // initialization up
 add    $0x1,%ebx         // ++i (now i is stored in register)
 cmp    %ebx,-0x5c(%ebp)  // compare i and size (which is load from memory)
 je     0x80488a3 <main(int, char**)+136>  // jump if = (and not >=)