【问题标题】:Why do you need "extern C" for C++ callbacks to C functions?为什么 C++ 回调 C 函数需要“extern C”?
【发布时间】:2010-04-07 16:31:49
【问题描述】:

我在 Boost 代码中找到了这样的例子。

namespace boost {
   namespace {
     extern "C" void *thread_proxy(void *f)
     {
       ....
     }

   } // anonymous
   void thread::thread_start(...)
   {
       ...
       pthread_create(something,0,&thread_proxy,something_else);
       ...
   }
} // boost

为什么你真的需要这个extern "C"

很明显thread_proxy 函数是私有的内部函数,我不希望它 将被修改为“thread_proxy”,因为我实际上根本不需要它。

事实上,在我编写并在许多平台上运行的所有代码中,我从未使用过extern "C",而且它在正常功能下都可以正常工作。

为什么要添加extern "C"


我的问题是extern "C" 函数污染了全局命名空间,它们实际上并没有像作者期望的那样隐藏。

这不是重复的! 我不是在谈论修饰和外部链接。在这段代码中很明显不需要外部链接!

答案: C 和 C++ 函数的调用约定不一定相同,所以需要创建一个符合 C 调用约定的。请参阅 C++ 标准的 7.5 (p4)。

【问题讨论】:

  • @Billy ONeal 这不是骗人的......正如我所说的那样,那里不需要修改。我知道如何使用 extern "C" 来解决管理问题。
  • @Artyom:名称修改是支持命名空间所必需的——您抱怨的功能 extern "C" 与之冲突。
  • @Billy 我知道这一点!但即使不需要链接,仍然使用“extern C”!
  • @Billy ONeal:C 回调不需要外部链接。

标签: c++ c callback extern-c


【解决方案1】:

很明显,thread_proxy 函数是私有的内部函数,我不认为它会被修改为“thread_proxy”,因为我实际上根本不需要它。

无论如何,它仍然会被破坏。 (如果不是extern "C")编译器就是这样工作的。我同意编译器可以说“这不一定需要修改”是可以想象的,但标准没有说明它。话虽如此,这里并没有发挥作用,因为我们没有尝试链接到该函数。

事实上,在我编写并在许多平台上运行的所有代码中,我从未使用过extern "C",而这与正常功能一样工作。

在不同平台上写作与extern "C"无关。我希望所有标准 C++ 代码都能在具有标准 C++ 兼容编译器的所有平台上运行。

extern "C" 与 C 接口有关,pthread 是 C 的库。它不仅不会破坏名称,而且确保它可以使用 C 调用约定进行调用。这是需要保证的调用约定,因为我们不能假设我们在某个编译器、平台或架构上运行,所以尝试这样做的最佳方法是使用提供给我们的功能:extern "C"

我的问题是extern "C" 函数污染了全局命名空间,它们实际上并没有像作者期望的那样隐藏。

上面的代码没有任何污染。它位于未命名的命名空间中,无法在翻译单元之外访问。

【讨论】:

  • 您能否给出任何平台/编译器的名称,其中 C 普通函数调用约定在 ABI 级别与 C++ 不兼容?因为至少在 Alpha、Itanium、ARM、X86、x86_64 上是这样。
  • @Artyom:不在 x86 上。您的编译器完全有可能在所有外部“C”函数上使用__cdecl,否则__stdcall__stdcall 生成更高效(更小)的代码,但由于被调用者清理堆栈,因此不允许创建可变参数函数。由于__cdecl 是大多数 C 世界的标准,因此要求外部代码调用约定是有意义的。
  • Mangling 此处不进图。仅当按名称链接函数时,重整才有意义;这里它的地址直接作为参数传递给其他函数。
  • @Artyom -- x86,Borland C++ 编译器。默认情况下,它对 C++ 函数使用 fastcall。
  • @GMan:在 Linux (Ubuntu) 上使用 g++ 4.4,我在翻译单元外也可以看到这个符号。我相信将符号标记为内部链接的正确方法是将其声明为外部“C”内的静态,因为外部“C”函数会忽略它们所在的任何命名空间(包括未命名的命名空间)。
【解决方案2】:

extern "C" 链接并不一定意味着只有名称修改被抑制。事实上,可能有一个编译器将extern "C" 视为一种不同的调用约定。

标准将其完全开放为实现定义的语义。

【讨论】:

  • 您能否给出任何平台的名称,其中 C 普通函数调用约定在 ABI 级别与 C++ 不兼容?因为至少在 Alpha、Itanium、ARM、X86、x86_64 上,我从来没有遇到过任何不同约定的问题。
  • 这有什么关系?您是否想编写仅因为您非常熟悉曾经编写的每个 C++ 实现的调用约定而有效的代码,并应用这些知识?或者您是否想编写适用于 C++ 标准的任何实现的代码,因为您按照标准要求您必须为您想要的结果做的事情?将来,您是否要编写标准,因为有人指出了一个确实需要 extern "C" 的实现,还是因为实际上您并不熟悉每个编译器?
  • @Artyom:C++ 调用约定是 C++ 编译器而非处理器的功能,大多数编译器允许配置用于编译 C++ 的约定。 Boost 设计为可移植的,因此如果用户更改编译器选项,它可能不会中断。
【解决方案3】:

这个问题是有效的 - 尽管函数被传递给 C 库,但该 C 库根本没有链接到 C++ 代码。它只给出了函数的地址,所以它对函数的名称完全没有兴趣。

关键是extern "C" 是最接近跨平台的方法,它告诉编译器让函数使用该平台上的标准 C 调用约定(即应该如何传递参数和返回值)在堆栈上)。

不幸的是,它还具有在全局级别创建外部链接器符号的副作用。但这可以通过改用 boost_detail_thread_proxy 之类的名称来缓解。

【讨论】:

  • 我知道解决方案之一是在变量中添加“boost_”前缀,仅此而已。我只是想知道为什么不删除extern "C"
【解决方案4】:

它用于使函数使用编译器通过 C 调用约定理解的任何内容,同时避免编译器特定的关键字,例如 __cdecl


仅此而已。这与名称修改、命名空间或任何其他奇怪的答案完全无关(正如您在询问时已经知道的那样)。

【讨论】:

    【解决方案5】:

    可能是因为您正在连接一个普通的 C 库 -- pthreads。

    【讨论】:

      【解决方案6】:

      由于不保证 C 和 C++ 具有相同的调用约定,因此您需要将回调函数声明为 extern "C" 以便将其传递给 pthread_create C 函数。

      上面的thread_proxy 函数具有外部链接(即在其翻译单元之外可见),因为命名空间对extern "C" 函数没有影响——即使是匿名命名空间。相反,要给thread_proxy 函数提供内部链接,您需要将其声明为静态:

      namespace boost {
          namespace {
              extern "C" {
                  static void *thread_proxy(void *f)
                  {
                      ....
                  }
              } // extern "C"
          } // anonymous
          ...
      } // boost
      

      [编辑] 请注意,boost 已包含此更改。见https://svn.boost.org/trac/boost/ticket/5170

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多