在 C/C++ for 语句参数内部的算术运算
In C/C++ arithmetic operation inside of for statement arguments
假设我有这个代码:
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
}
操作 v - 5
每次都是 运行 还是现代编译器会足够聪明,只存储一次就再也不会 运行 了?
如果我这样做会怎样:
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
}
第二种风格会有所不同吗?
编辑:
出于意想不到的原因,这是一个有趣的问题。这更多是编译器的一个问题,它避免了 v
的意外别名的钝化情况。如果编译器可以证明这不会发生(版本 2),那么我们会得到更好的代码。
这里的教训是更关注消除混叠,而不是尝试为它做优化器的工作。
制作 cv 副本实际上提供了最大的优化(冗余内存提取的省略),即使乍一看它似乎(稍微)效率较低。
原始答案和演示:
让我们看看:
给定:
extern void setV(int*);
extern void do_something(int i);
void test1()
{
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
do_something(i);
}
}
void test2()
{
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
do_something(i);
}
}
使用 -x c++ -std=c++14 -O2 -Wall
在 gcc5.3 上编译
给出:
test1():
pushq %rbx
subq , %rsp
leaq 12(%rsp), %rdi
call setV(int*)
cmpl , 12(%rsp)
jle .L1
xorl %ebx, %ebx
.L5:
movl %ebx, %edi
addl , %ebx
call do_something(int)
movl 12(%rsp), %eax
subl , %eax
cmpl %ebx, %eax
jg .L5
.L1:
addq , %rsp
popq %rbx
ret
test2():
pushq %rbp
pushq %rbx
subq , %rsp
leaq 12(%rsp), %rdi
call setV(int*)
movl 12(%rsp), %eax
cmpl , %eax
jle .L8
leal -5(%rax), %ebp
xorl %ebx, %ebx
.L12:
movl %ebx, %edi
addl , %ebx
call do_something(int)
cmpl %ebp, %ebx
jne .L12
.L8:
addq , %rsp
popq %rbx
popq %rbp
ret
第二种形式在此编译器上更好。
假设我有这个代码:
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
}
操作 v - 5
每次都是 运行 还是现代编译器会足够聪明,只存储一次就再也不会 运行 了?
如果我这样做会怎样:
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
}
第二种风格会有所不同吗?
编辑:
出于意想不到的原因,这是一个有趣的问题。这更多是编译器的一个问题,它避免了 v
的意外别名的钝化情况。如果编译器可以证明这不会发生(版本 2),那么我们会得到更好的代码。
这里的教训是更关注消除混叠,而不是尝试为它做优化器的工作。
制作 cv 副本实际上提供了最大的优化(冗余内存提取的省略),即使乍一看它似乎(稍微)效率较低。
原始答案和演示:
让我们看看:
给定:
extern void setV(int*);
extern void do_something(int i);
void test1()
{
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
do_something(i);
}
}
void test2()
{
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
do_something(i);
}
}
使用 -x c++ -std=c++14 -O2 -Wall
给出:
test1():
pushq %rbx
subq , %rsp
leaq 12(%rsp), %rdi
call setV(int*)
cmpl , 12(%rsp)
jle .L1
xorl %ebx, %ebx
.L5:
movl %ebx, %edi
addl , %ebx
call do_something(int)
movl 12(%rsp), %eax
subl , %eax
cmpl %ebx, %eax
jg .L5
.L1:
addq , %rsp
popq %rbx
ret
test2():
pushq %rbp
pushq %rbx
subq , %rsp
leaq 12(%rsp), %rdi
call setV(int*)
movl 12(%rsp), %eax
cmpl , %eax
jle .L8
leal -5(%rax), %ebp
xorl %ebx, %ebx
.L12:
movl %ebx, %edi
addl , %ebx
call do_something(int)
cmpl %ebp, %ebx
jne .L12
.L8:
addq , %rsp
popq %rbx
popq %rbp
ret
第二种形式在此编译器上更好。