Has*_*yed 17 c++ micro-optimization
template <typename TAG>
fn(int left, TAG, int right)
{
}
fn(0, some_type_tag(), 1);
/* or */
fn(0,int(), 1); // where the primitive, int, is not empty.
Run Code Online (Sandbox Code Playgroud)
编辑:这个问题有两个视角.
/编辑
我的标签通常是空结构,但是在我的代码的某些部分,它们是原始类型的typedef.所以,我很想知道现代编译器是否会实际传递参数.这有两个方面.
让我们把它保持到gcc 4.5和msvc 2008+
MSa*_*ers 12
C++有单独的翻译.由于参数可以在声明中命名但不能在函数定义中命名,反之亦然,因此编译器通常无法知道省略函数参数是否安全.当它全部在同一个翻译单元中时,所有内容都可以内联,参数名称与优化完全无关.
[添加]
单独的转换对于这种特定情况可能无关紧要,但是添加这种优化的编译器构建器必须关注.如果它破坏了完全有效的代码,它们就不会进行这样的优化.
对于模板,模板函数的类型必须等于非模板函数的类型,否则不可能获取其地址并将其分配给函数指针.同样,你必须考虑单独的翻译.仅仅因为你没有拿到foo<int>这个TU 的地址并不意味着你不会在另一个.
参数是否已命名对函数签名没有影响,编译器应将其传入.请考虑函数声明中的未命名参数可能在定义中命名.
现在,在上述模板的特定情况下,编译器可能会内联代码,在这种情况下不会传递任何参数,并且未命名的参数将不起作用.
如果你要做的是标记以解决不同的重载,你总是可以回退到一个指针,这样即使传入它,成本也会降到最低.
实际上这是一个非常有趣的问题.
首先,请注意,我们是在命令式语言,也就是说,当你问的东西(甚至是无用的,比如构建一个未使用的对象),那么编译器必须遵守,除非能拿出一个等价形式.基本上,如果可以证明这样做不会改变程序的含义,它可能会忽略参数.
当你编写函数调用时,可能会发生两件事(最后):
call实际上是a如果是内联,则没有参数传递,这实际上意味着未使用的对象可以被删除(甚至没有建)如果编译器可以证明所涉及的构造和析构不进行任何显著的工作.它适用于标签结构.
发出调用时,将使用特定的调用约定发出调用.每个编译器都有自己的一组调用约定,它们指定如何传递各种参数(this指针等),通常试图利用可用的寄存器.
由于只使用函数声明来确定调用约定(单独的编译模型),因此有必要实际传递对象...
但是,如果我们讨论的是一个空结构,没有方法也没有状态,那么这只是一些未初始化的内存.它不应该花费太多,但它确实需要堆栈空间(至少,保留它).
使用llvm试用版进行演示:
struct tag {};
inline int useless(int i, tag) { return i; }
void use(tag);
int main() {
use(tag());
return useless(0, tag());
}
Run Code Online (Sandbox Code Playgroud)
得到:
%struct.tag = type <{ i8 }>
define i32 @main() {
entry:
; allocate space on the stack for `tag`
%0 = alloca %struct.tag, align 8 ; <%struct.tag*> [#uses=2]
; get %0 address
%1 = getelementptr inbounds %struct.tag* %0, i64 0, i32 0 ; <i8*> [#uses=1]
; 0 initialize the space used for %0
store i8 0, i8* %1, align 8
; call the use function and pass %0 by value
call void @_Z3use3tag(%struct.tag* byval %0)
ret i32 0
}
declare void @_Z3use3tag(%struct.tag* byval)
Run Code Online (Sandbox Code Playgroud)
注意:
useless删除调用,并且没有为它构建参数use无法删除,因此为临时分配空间(我希望新版本不要0初始化内存)