在某些情况下,我们使用标签来区分功能.标记通常是一个空结构:
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”函数),否则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());
}
另外,请注意,虽然编译器可能会生成初始目标文件中的函数调用,但链接时优化可能最终可以去掉未使用的参数.至少有一个主要编译器甚至documents这样的行为……