【问题标题】:Passing C++ struct to library expecting C struct将 C++ 结构传递给需要 C 结构的库
【发布时间】:2018-09-18 06:52:29
【问题描述】:

我正在 Linux 中编写一个 C++ 程序,需要使用一个用 C 编写的旧库。该库使用 C 结构来将信息传入和传出函数,并且这些结构是字节对齐的(无填充)。

我的理解是,C++ 中的 struct 实际上是一个对象,而 C 中的 struct 只是一块内存,被划分为可单独寻址的部分。

如何在 C++ 中创建 C 风格的结构以传递给库? (我不能传递对象)

【问题讨论】:

  • 你的理解是错误的。结构在 C 和 C++ 中基本相同。在 C 中定义的结构将在 C++ 中编译和链接
  • 如果结构成员在 C 结构中有效,则它们是等价的。例如,添加虚函数会使结构不兼容。
  • 我只能猜测“实际上是一个对象”对你意味着什么。不管它是什么,它都不适用于 C++。
  • 一个 C++ 类(由 struct 关键字或 class 关键字声明的)可以是很多东西,但 通常 它类似于一个 C 结构。 en.cppreference.com/w/cpp/concept/StandardLayoutTypeen.cppreference.com/w/cpp/concept/TrivialType 可能会有所帮助。
  • 这个问题很难回答,因为有很多问题需要考虑。简短且不完整的答案是,符合“标准布局”类(任何纯 C 声明都可以)要求的 C++ struct(或 class)就可以了。 extern "C" 在这里完全是红鲱鱼,根本不相关。

标签: c++ c linux struct


【解决方案1】:

你在这里问了两个问题,真的......

如何在 C++ 中创建 C 风格的结构?

代替

struct foo { /* ... */ };

使用

extern "C" {
    struct foo { /* ... */ };
}

这可能不会导致任何不同,即“C++ 样式结构”和“C 样式结构”通常是相同的,只要您不添加方法、受保护的成员和位字段。但是,由于函数需要“extern C”,因此在这些大括号内将所有打算在 C 中使用的代码括起来是合理的。

更多详情,请阅读:What is the effect of extern "C" in C++? 和@AndrewHenle 的评论。


我...需要使用用 C 编写的库

我在这里转述official C++ FAQ item,告诉你(惊讶,惊讶)只需将库头包含在 extern C 块中,然后像编写 C 一样使用其中的任何内容:

extern "C" {
  // Get declaration for `struct foo` and for `void f(struct foo)`
  #include "my_c_lib.h"
}

int main() {
    struct foo { /* initialization */ } my_foo;
    f(my_foo);
}

【讨论】:

  • 我现在明白你的意思了。如果可以的话,我建议您进一步扩展它并解决包含在语言链接块中的 C 标头。因为这就是 OP 似乎正在处理的问题。
  • 将结构包装在 extern "C" 中会导致结构被打包到字节级别吗?对于库来说,将其包装在命名空间中是否有好处,这样它就不会污染全局地址空间?
  • @TSG:不,不会。并且 - C 中的结构也不会自动打包。打包是(AFAIK)使用编译器特定的扩展完成的。
  • 值得注意的是,您实际上也可以继承 C 结构,只要您不使用任何会改变布局并创建 v-table 的 virtual 函数
  • @Omnifarious 这是错误信息。 extern "C"struct 布局完全无关。 不完全正确。有办法。 struct 的某些部分可以是实现定义的(位字段),并且在 C++ 代码中使用 C struct 很可能通过不同的实现来完成,尤其是当它类似于本文中的“旧库”时题。 FWIW,我还遇到了库和调用应用程序之间不同的编译器优化选项导致不同的struct 布局的情况。想象一下使用 GCC 和 -fpack-struct 编译库。
【解决方案2】:

我的理解是,C++ 中的 struct 实际上是一个对象,而 C 中的 struct 只是一块内存,被划分为可单独寻址的部分。

你刚刚用不同的词描述了几乎完全相同的事情。

由于两种语言的语法重叠,您通常可以采用使用 struct 关键字定义的简单类,并使用 C 编译器编译相同的定义。

此外,您通常可以将指向用户定义类型对象的指针从 C++ 程序传递给 C 程序。但是,您通常希望将定义包装在 extern "C" 中,以便告诉计算机您需要这种兼容性。

最后,请记住,在 C++ 中,关键字 class 和关键字 struct 都引入了一个类定义,因此 C++ 并没有真正的“结构”,但在 C 中它们绝对是一件事(而类不是)。因此,请注意术语。

【讨论】:

    【解决方案3】:

    所以,这个问题有一些与之相关的复杂问题。但是,大致来说,答案很简单。

    任何将被 C 编译器和 C++ 编译器(其中大多数被 C 编译器接受)都接受的 C struct 声明将是“Standard Layout Type”。这是 C++ 标准中定义的概念,C++ 编译器应该以某种方式对待它们。

    特别是,符合 C++ 编译器所遵循的 ABI (aka Application Binary Interface) 子集的 C 编译器应该具有与此类类型完全相同的内存表示。

    对于 Linux 和 Windows,针对 C 和 C++ 都非常仔细地定义了 ABI,并且已经有 5 到 10 年的历史了。任何一个平台上的所有编译器都应该符合它。因此,对于任何常见的 C 和 C++ 编译器组合都是如此。这包括 Clang、g++、Visual Studio,以及几乎所有在该平台上工作的非高度专业化的编译器。这并不一定意味着标准 C++ 库的不同版本具有兼容的实现,因为(例如)多年来实现 ::std::string 的方式(因此字符串结构中实际存在哪些数据)已经发生了巨大变化即使公共界面没有太大变化。

    C++ ABI 因几个不同的因素而变得复杂。其中包括异常处理和函数名称修改(在函数的链接器符号名称中编码函数参数的类型)约定。此外,由于在很多情况和其他问题中类型默认为int 的方式,C 很大程度上要求调用者在函数完成后从堆栈中弹出参数,因为被调用的函数不能真正确定被推的论点。 C++ 有更严格的类型检查,有一段时间这导致了“被调用的函数弹出参数”的调用约定。我认为不再是这种情况了,因为我认为事实证明它的效率较低。不过我可能错了。

    现在,如果您开始使用特定于编译器的关键字(例如“packed”或“aligned”),这一切都会消失。然后,您应该仔细查阅文档以了解会发生什么。

    人们提到了extern "C" 声明。这些对于 C 和 C++ 代码之间的接口很重要,但它们与 struct 布局无关,根本不需要。 extern "C" 声明的好处是声明函数名和调用约定符合 C ABI 而不是 C++ ABI。这是关于调用者是否从堆栈中弹出函数调用参数或被调用函数,函数名称如何转换为链接器使用的符号,顺序参数被压入堆栈等等。同样,关于结构在内存中的布局方式与extern "C" 无关。这个结构是关于函数的,而不是structs。

    而且,重申一下,这里有很多复杂性。但在绝大多数情况下,您根本不需要担心。它会起作用的。

    【讨论】:

    • 这个答案对于 Windows 来说是完全错误的。 Microsoft 编译器团队已经发布了大量关于 windows 如何工作以及其他编译器工作所需的开源组件的信息。 Chrome 现在可以在 windows 上使用 clang 构建。 Microsoft 特定的调用约定已经记录了很长时间,并且 mingw 和 icc 也支持了很多年。
    • 因为微软已经积极阻止它的出现,希望防止来自其平台上的竞争编译器的竞争”我很好奇这个声明的基础。 Windows 确实有一个标准的 ABI。只是不是 Linux 使用的 same 标准(Itanium)。毕竟,您不能随心所欲地更改 ABI。这会破坏几乎所有没有重新编译的程序。
    • 为了清楚起见,请跟进 32bit x86 x86-64 ARM 的 Windows 调用约定。
    • @Mgetz - 我说的是 C++。我所知道的 C++ 的唯一标准是 COM,从这个词的正常意义来看,这根本不是真正的 ABI。是的,我同意,C 的东西在很大程度上是标准化的。 C++ 的东西根本不是。将 g++ 或 Clang 编译的代码链接到 Visual C++ 编译的代码基本上是不可能的。至少,在 SQL Server for Windows 上工作的每个人都认为情况就是如此。
    • @Omnifarious 请不要混淆 ABI 的名称修改,C++ 使用与 C 相同的 ABI 实践。不同之处在于有些东西是编译器版本安全的(COM,extern 'C')和不是std::string 等。在Linux 上(通常)只有一个编译器和stdlib 同时运行,因此不必担心ABI 不兼容的C++ 库对象不是问题。在 Windows 上,指南一直是它们在编译器和库组合中是安全的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多