【问题标题】:How compilers handle the ABI correctly when calling a function by pointer in C/C++?在 C/C++ 中通过指针调用函数时,编译器如何正确处理 ABI?
【发布时间】:2018-04-06 08:49:27
【问题描述】:

编译器如何知道函数指针指向的函数的ABI?

在计算机软件中,应用程序二进制接口 (ABI) 是 两个程序模块之间的接口。

由于在 C/C++ 中,函数指针仅指定 API,因此不知道它实际使用的是哪种 ABI,这对编译器来说不会是个问题,尤其是当他们无法静态计算出来的时候?

这是否意味着使用这种指针的程序员需要手动指定调用约定?

如果是这样,该怎么做?谁能给我一些关于这个编译器文档的链接?

【问题讨论】:

  • 这就是为什么编译器在同一系统上一起工作时尝试使用相同的 ABI。对于同一种语言,某些编译器在它们之间不兼容。
  • “这是否意味着使用这种指针的程序员需要手动指定调用约定”:是(通常在包含文件中)
  • 您还可以在函数指针上指定调用约定。例如,MSDN 列出了以下示例:typedef BOOL (__stdcall *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);(请参阅msdn.microsoft.com/en-us/library/zxk0tw93%28v=VS.100%29.aspx)。
  • @pschill:这种做法不是 MSVC 特有的吗?
  • @einpoklum 是的。其他编译器可能使用不同的语法。

标签: c++ c function function-pointers


【解决方案1】:

如果函数使用的调用约定与您所在平台的默认调用约定不同,那么是的,您需要手动指定调用约定。大多数平台尝试使用单个 ABI,但具有单个或可预测的调用约定集,因此任何编译器都知道如何调用任何函数。

虽然这超出了 C++ 的范围,但如果您可以或需要指定调用约定,则可以使用您正在使用的编译器的非标准扩展来完成。

【讨论】:

    【解决方案2】:

    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);
    }
    

    编译器不允许将函数指针转换为指向不同调用约定的函数的指针。

    错误:从int (__attribute__((fastcall)) *)(int, int)int (*)(int, int) 的无效转换[-fpermissive]

      call1(a, b, sum2);
                  ^~~~
    

    godbolt上玩它

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-01
      • 2019-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-13
      • 1970-01-01
      相关资源
      最近更新 更多