优化了标签(空结构)函数参数的处理
Optimized handling of tag (empty struct) function parameters
在某些情况下,我们使用标签来区分函数。标签通常是一个空结构:
struct Tag { };
假设我有一个函数,它使用这个标签:
void func(Tag, int a);
现在,让我们调用这个函数:
func(Tag(), 42);
并查看由此产生的 x86-64 拆卸,godbolt:
mov edi, 42
jmp func(Tag, int) # TAILCALL
没关系,标签被完全优化了:没有为其分配寄存器/堆栈空间。
但是,如果我查看其他平台,该标签有一些存在感。
在 ARM 上,r0
用作标签,它被清零(似乎没有必要):
mov r1, #42
mov r0, #0
b func(Tag, int)
使用 MSVC,ecx
用作标记,并从堆栈"初始化"(同样,似乎没有必要):
movzx ecx, BYTE PTR $T1[rsp]
mov edx, 42 ; 0000002aH
jmp void func(Tag,int) ; func
我的问题是:是否有一种标签技术,在所有这些平台上都进行了同样优化?
注意:我没有找到 SysV ABI 在哪里指定空类可以在参数传递时进行优化......(甚至,Itanium C++ ABI 说:"空类的传递与普通类没有什么不同"。
我认为这里的基本问题是,在生成函数的独立版本时,编译器必须生成代码,任何人都可以根据各自的调用约定从任何地方调用。当在不知道函数定义的情况下生成对函数的调用时,编译器真正知道的是该函数希望根据调用约定进行调用。基于此,除非调用约定指定删除空类型的函数参数,否则编译器通常无法真正优化函数调用中的参数。现在,对于C++编译器来说,当场编造它认为适合给定函数签名的任何调用约定在技术上可能是合法的,除非该函数具有非C++语言链接(例如,extern "C"
函数)。但实际上,这很可能不是那么简单。首先,您需要一种算法来决定给定函数签名的最佳调用约定通常是什么样子。其次,能够链接不一定全部使用完全相同的编译器的完全相同版本的完全相同的编译器生成的代码,使用完全相同的标志,虽然C++标准没有要求,但在实践中可能是相关的。函数调用约定优化当然不是不可能。但我不知道有任何C++编译器实际执行此操作(在生成目标代码时)。
一种可能的解决方案是,例如,为实际的函数实现使用不同的名称,并具有简单的内联包装器函数,将带有 Tag 类型的调用转换为相应的实现:
struct TagA { };
struct TagB { };
inline void func(int a, TagA)
{
void funcA(int a);
funcA(a);
}
inline void func(int a, TagB)
{
void funcB(int a);
funcB(a);
}
void call() {
func(42, TagA());
func(42, TagB());
}
在这里尝试一下
另外,请注意,虽然编译器可能会生成类似于初始对象文件中的函数调用,但链接时优化最终可能能够删除未使用的参数。至少有一个主要的编译器甚至记录了这种行为......
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- C++中的高效循环缓冲区,它将被传递给C样式数组函数参数
- 当从函数参数中的临时值调用复制构造函数时
- 如何从"decltype()"获取函数参数的数量<funtion>?
- 如何将lambda作为模板类的成员函数参数
- 模板参数推导失败,函数参数/参数不匹配
- 如何在C++中将迭代器作为函数参数传递
- 将函数参数"const char*"转换为"std::string_view"是
- C++ 如何将数组值解压缩为函数参数
- 主函数参数的属性
- 具有两个间接寻址运算符 (C++) 的函数参数的用途
- "Warning: Comma within array index expression"但逗号分隔函数参数
- 如何定义在用作函数参数时工作的类模板的转换
- 将函数参数完美转发到函数指针:按值传递呢?
- 为什么我不能将引用作为 std::async 的函数参数传递
- 什么..(省略号)作为函数原型中唯一的函数参数,C++?
- 是否可以就地构造一个固定大小的数组作为函数参数?
- 接受模板作为函数参数
- 将成员函数作为构造函数参数调用时出错 "Variable is not a type name"
- Arduino 函数参数