在 C/C++ 中通过指针调用函数时,编译器如何正确处理 ABI?
How compilers handle the ABI correctly when calling a function by pointer in C/C++?
编译器如何知道函数指针指向的函数的 ABI?
In computer software, an application binary interface (ABI) is an
interface between two program modules.
因为在 C/C++ 中函数指针只指定 API,所以不知道它实际使用的是哪种 ABI,这对编译器来说不是问题,尤其是当它们无法静态地解决这个问题?
这是否意味着使用这种指针的程序员需要手动指定调用约定?
如果是这样,怎么办?谁能给我一些 link 编译器文档?
如果函数使用的调用约定不同于您所在平台的默认调用约定,那么是的,您需要手动指定调用约定。大多数平台都尝试使用单一的 ABI,尽管有一组单一的或可预测的调用约定,因此任何编译器都知道如何调用任何函数。
虽然这超出了 C++ 的范围,但如果您可以或需要指定调用约定,可以使用您正在使用的编译器的 non-standard 扩展来完成。
gcc 和我怀疑所有主要编译器都通过让函数(和指向此类函数的指针)具有针对不同调用约定的不同类型来解决此问题。让我们看看:
__attribute__ ((noinline)) auto sum1(int a, int b) { return a + b; }
__attribute__ ((noinline)) auto sum2(int a, int b) __attribute__((fastcall));
auto sum2(int a, int b) { return a + b; }
auto test1(int a, int b) { return sum1(a, b); }
auto test2(int a, int b) { return sum2(a, b); }
sum1(int, int):
mov eax, DWORD PTR [esp+8]
add eax, DWORD PTR [esp+4]
ret
sum2(int, int):
lea eax, [ecx+edx]
ret
test1(int, int):
jmp sum1(int, int)
test2(int, int):
mov edx, DWORD PTR [esp+8]
mov ecx, DWORD PTR [esp+4]
jmp sum2(int, int)
上面我们可以很明显的看出这两个函数的调用方式不同
当我们在混合中使用指针时会发生什么:
__attribute__ ((noinline)) auto call1(int a, int b, auto (*f)(int, int) -> int)
{
return f(a, b);
}
__attribute__ ((noinline))
auto call2(int a, int b, auto (__attribute__((fastcall)) *f )(int, int) -> int )
{
return f(a, b);
}
auto test(int a, int b)
{
call1(a, b, sum1);
// call2(a, b, sum1); // compiler error
// call1(a, b, sum2); // compiler error
call2(a, b, sum2);
}
编译器不允许将函数指针转换为指向不同调用约定的函数的指针。
error: invalid conversion from int (__attribute__((fastcall)) *)(int,
int)
to int (*)(int, int)
[-fpermissive]
call1(a, b, sum2);
^~~~
在 godbolt
上玩一下
编译器如何知道函数指针指向的函数的 ABI?
In computer software, an application binary interface (ABI) is an interface between two program modules.
因为在 C/C++ 中函数指针只指定 API,所以不知道它实际使用的是哪种 ABI,这对编译器来说不是问题,尤其是当它们无法静态地解决这个问题?
这是否意味着使用这种指针的程序员需要手动指定调用约定?
如果是这样,怎么办?谁能给我一些 link 编译器文档?
如果函数使用的调用约定不同于您所在平台的默认调用约定,那么是的,您需要手动指定调用约定。大多数平台都尝试使用单一的 ABI,尽管有一组单一的或可预测的调用约定,因此任何编译器都知道如何调用任何函数。
虽然这超出了 C++ 的范围,但如果您可以或需要指定调用约定,可以使用您正在使用的编译器的 non-standard 扩展来完成。
gcc 和我怀疑所有主要编译器都通过让函数(和指向此类函数的指针)具有针对不同调用约定的不同类型来解决此问题。让我们看看:
__attribute__ ((noinline)) auto sum1(int a, int b) { return a + b; }
__attribute__ ((noinline)) auto sum2(int a, int b) __attribute__((fastcall));
auto sum2(int a, int b) { return a + b; }
auto test1(int a, int b) { return sum1(a, b); }
auto test2(int a, int b) { return sum2(a, b); }
sum1(int, int):
mov eax, DWORD PTR [esp+8]
add eax, DWORD PTR [esp+4]
ret
sum2(int, int):
lea eax, [ecx+edx]
ret
test1(int, int):
jmp sum1(int, int)
test2(int, int):
mov edx, DWORD PTR [esp+8]
mov ecx, DWORD PTR [esp+4]
jmp sum2(int, int)
上面我们可以很明显的看出这两个函数的调用方式不同
当我们在混合中使用指针时会发生什么:
__attribute__ ((noinline)) auto call1(int a, int b, auto (*f)(int, int) -> int)
{
return f(a, b);
}
__attribute__ ((noinline))
auto call2(int a, int b, auto (__attribute__((fastcall)) *f )(int, int) -> int )
{
return f(a, b);
}
auto test(int a, int b)
{
call1(a, b, sum1);
// call2(a, b, sum1); // compiler error
// call1(a, b, sum2); // compiler error
call2(a, b, sum2);
}
编译器不允许将函数指针转换为指向不同调用约定的函数的指针。
error: invalid conversion from
int (__attribute__((fastcall)) *)(int, int)
toint (*)(int, int)
[-fpermissive]call1(a, b, sum2); ^~~~
在 godbolt
上玩一下