将C++结构体传递给期望C结构体的库

3

我正在Linux中编写C++程序,需要使用一个用C语言编写的旧库。该库使用C结构体在函数内外传递信息,这些结构体是字节对齐的(无填充)。

我理解,在C++中,结构体实际上是一个对象,而在C中结构体只是一个被分割成可单独寻址的内存块。

如何在C++中创建一个C风格的结构体以传递给库?(无法传递一个对象)


11
你的理解有误。在C和C++中,结构体基本上是相同的。在C中定义的结构体可以在C++中编译和链接。 - user2100815
9
如果结构体成员在C结构体中是有效的,则它们是等效的。例如,添加虚函数将使结构体不兼容。 - Bo Persson
1
我只能猜测“实际上是一个对象”对你来说意味着什么。无论它是什么,它都不适用于C++。 - Jive Dadson
一个C++类(由struct关键字或class关键字声明)可以是许多东西,但通常它类似于C结构体。http://en.cppreference.com/w/cpp/concept/StandardLayoutType和http://en.cppreference.com/w/cpp/concept/TrivialType可能会有所帮助。 - hegel5000
2
这个问题很棘手,因为有很多问题需要考虑。简短而不完整的答案是,符合“标准布局”类要求(任何纯C声明都符合)的C++ struct(或class)是可以的。在这里,“extern C”是一个完全无关紧要的东西,与此毫不相关。 - Omnifarious
在C++中,每个对象都是“只是一个内存块,被分成可单独寻址的部分”。 - Caleth
3个回答

5
您在这里实际上是在问两个问题...

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

不要使用

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

使用

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

这可能不会有任何不同,即“C++风格的结构体”和“C风格的结构体”通常是相同的,只要你不添加方法、受保护的成员和位域。由于函数需要“extern C”,因此在这些大括号中包含所有用于C中使用的代码是合理的。
更多细节,请阅读:【Stack Overflow】在C++中使用extern "C"的作用是什么? 和 @AndrewHenle的评论。

我需要使用一个用C写的库

我在这里引述一个官方C++ FAQ项的意思,建议您(惊奇吧)只需在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);
}

将结构体包装在extern "C"中会导致结构体被按字节级别进行打包吗?对于库来说,将其包装在命名空间中是否有益于避免污染全局地址空间? - TSG
@TSG:不,它不会。而且,在C语言中结构体也不是自动打包的。据我所知,打包是使用特定于编译器的扩展进行的。 - einpoklum
这是错误信息。extern "C"struct布局毫无关系。 - Omnifarious
@Omnifarious:我在回答的正文中已经说过了,但让我再澄清一下。 - einpoklum
2
@Omnifarious 这是错误信息。extern "C"struct布局没有任何关系。 不完全正确。有些部分的struct可以是实现定义的(位域),在C++代码中使用C struct 很可能会使用不同的实现,特别是当它是像这个问题中的“旧库”时。FWIW,我也遇到过库和调用应用程序之间不同编译器优化选项导致不同的struct布局的情况。想象一下使用GCC和-fpack-struct编译库。 - Andrew Henle
显示剩余6条评论

1
我的理解是,在C++中,结构体实际上是一个对象,而在C中,结构体只是一个被划分成可单独寻址的内存块。
你刚刚用不同的措辞描述了几乎完全相同的事情。
由于两种语言的语法重叠,通常可以使用带有“struct”关键字的简单类定义,并使用C编译器编译相同的定义。
此外,通常可以将用户定义类型的对象指针从C++程序传递到C程序。但是,通常需要在定义中包装“extern"C"”,以告诉计算机您想要这种兼容性。
最后,请记住,在C++中,“class”关键字和“struct”关键字都引入了“类定义”,因此C++实际上没有“结构体”,但在C中它们绝对是一种东西(而类则不是)。因此,术语要小心使用。

0

所以,这个问题涉及到一些复杂的问题。但是,初步的答案很简单。

任何一个C语言struct声明,只要被C编译器和C++编译器都接受(大多数被C编译器接受的也被C++编译器接受),都将是“标准布局类型”。这是C++标准中定义的一个概念,C++编译器应该以某种方式处理它们。

特别地,符合C++编译器子集ABI(也称应用程序二进制接口)的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"只与函数有关,而不是struct
此外,需要重申的是,这里有很多复杂性。但在绝大多数情况下,您根本不需要担心它。它会正常工作。

4
就 Windows 来说,这个回答完全是错误的。微软编译器团队已经发布了大量关于 Windows 工作原理的信息,并开源了其他编译器所需的组件。以至于 Chrome 现在在 Windows 上使用 clang 进行构建。微软特定的调用约定早已有文档记录,并且 MinGW 和 ICC 也支持了很长时间。 - Mgetz
“由于微软积极阻止其出现,希望防止平台上的竞争编译器竞争。”我对这个说法的基础很好奇。Windows确实有一个标准ABI;只是不同于Linux使用的标准(Itanium)。毕竟,你不能随意更改ABI;否则那将破坏几乎所有没有重新编译的程序。” - Nicol Bolas
@Mgetz- 我在谈论 C++。我所知道的唯一标准是 COM,但这并不是常规意义上真正的 ABI。是的,我会同意,C 语言部分大体上是标准化的,而 C++ 部分则完全没有标准化。基本上是不可能将由 g++ 或 Clang 编译的代码与由 Visual C++ 编译的代码进行链接。至少,在为 Windows 工作的 SQL Server 的所有人都认为是这种情况。 - Omnifarious
1
@Omnifarious请不要将名称混淆为ABI,C++使用与C相同的ABI实践。区别在于有些东西是编译器版本安全的(COM,extern 'C'),而有些东西则不是,例如std::string等。在Linux上,通常只有一个编译器和stdlib在运行,因此不必担心具有ABI不兼容的C++库对象。在Windows上,指南一直是它们在编译器和库组合内是安全的。 - Mgetz
@Mgetz - 已修复。 - Omnifarious
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接