编译器会优化这个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 >=)