【发布时间】:2016-06-22 10:21:06
【问题描述】:
我尝试使用 Clang 和 GCC 编译这段代码:
struct s { int _[50]; };
void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *);
main()
{
struct s a;
pF2(&a);
pF(a), pF1(a);
}
结果是一样的。尽管对pF 的调用不允许修改其唯一参数,但对象a 被复制以用于对pF1 的第二次调用。这是为什么呢?
这是汇编输出(来自 GCC):
; main
push rbx
sub rsp, 0D0h
mov rbx, rsp
mov rdi, rsp
call cs:pF2
;create argument for pF1 call (as there the argument is modified)
;and copy the local a into it
;although it seems not needed because the local isn't futher read anyway
sub rsp, 0D0h
mov rsi, rbx
mov ecx, 19h
mov rdi, rsp
;
rep movsq
call cs:pF
;copy the local a into the argument created once again
;though the argument cannot be modified by the function pointed by pF
mov rdi, rsp
mov rsi, rbx
mov ecx, 19h
rep movsq
;
call cs:pF1
add rsp, 1A0h
xor eax, eax
pop rbx
retn
难道优化器看不到pF 指向的函数不能修改它的参数(因为它被声明为const),所以忽略了最后的复制操作?另外,最近我看到,由于变量a 没有在代码中进一步读取,它可以将其存储用于函数参数。
同样的代码可以写成:
; main
push rbx
sub rsp, 0D0h
mov rdi, rsp
call cs:pF2
call cs:pF
call cs:pF1
add rsp, 0D0h
xor eax, eax
pop rbx
retn
我正在使用-O3 标志进行编译。我错过了什么吗?
即使我不调用 UB 也是一样的(因为函数指针默认为 NULL),而是将它们初始化为:
#include <stdio.h>
struct s { int _[50]; };
extern void f2(struct s *a);
void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *) = f2;
extern void f1(struct s a)
{
a._[2] = 90;
}
extern void f(const struct s a)
{
for(size_t i = 0; i < sizeof(a._)/sizeof(a._[0]); ++i)
printf("%d\n", a._[i]);
}
extern void f2(struct s *a)
{
a->_[6] = 90;
pF1 = f1, pF = f;
}
【问题讨论】:
-
你期望优化器知道一些它没有代码的东西。正如@JoachimPileborg 指出的那样,保持按值传递工作的唯一方法是给
pF和pF1一份副本。 -
@MichaelFoukarakis:我猜OP的目标是:编译器可以看到有问题的数据仍在堆栈上,不需要第二个副本,只需调整@987654336 @。但这只有在函数 impls 不做一些“非常有趣”的事情时才有效 - 例如将 const 丢弃。
-
@peterchen 像这样“有趣”的东西不会是 UB(因此默认情况下不是预期的)?
-
@peterchen 这个问题被标记为 c 而不是 c++。
-
参数类型的顶级 const 实际上并没有做任何事情。两个函数声明,其中一个将顶级 const 添加到某些参数类型是兼容的。 (这里有一个 C++ 现场演示:coliru.stacked-crooked.com/a/15a735168f34cd46)
标签: c gcc assembly optimization clang