我正在Linux中编写C++程序,需要使用一个用C语言编写的旧库。该库使用C结构体在函数内外传递信息,这些结构体是字节对齐的(无填充)。
我理解,在C++中,结构体实际上是一个对象,而在C中结构体只是一个被分割成可单独寻址的内存块。
如何在C++中创建一个C风格的结构体以传递给库?(无法传递一个对象)
我如何在C++中创建C风格的结构体?
不要使用
struct foo { /* ... */ };
使用
extern "C" {
struct foo { /* ... */ };
}
我在这里引述一个官方C++ FAQ项的意思,建议您(惊奇吧)只需在extern C块内包含库头文件,然后像编写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"
与struct
布局毫无关系。 - Omnifariousextern "C"
与struct
布局没有任何关系。 不完全正确。有些部分的struct
可以是实现定义的(位域),在C++代码中使用C struct
很可能会使用不同的实现,特别是当它是像这个问题中的“旧库”时。FWIW,我也遇到过库和调用应用程序之间不同编译器优化选项导致不同的struct
布局的情况。想象一下使用GCC和-fpack-struct
编译库。 - Andrew Henle所以,这个问题涉及到一些复杂的问题。但是,初步的答案很简单。
任何一个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
。extern 'C'
),而有些东西则不是,例如std::string
等。在Linux上,通常只有一个编译器和stdlib在运行,因此不必担心具有ABI不兼容的C++库对象。在Windows上,指南一直是它们在编译器和库组合内是安全的。 - Mgetz
struct
关键字或class
关键字声明)可以是许多东西,但通常它类似于C结构体。http://en.cppreference.com/w/cpp/concept/StandardLayoutType和http://en.cppreference.com/w/cpp/concept/TrivialType可能会有所帮助。 - hegel5000struct
(或class
)是可以的。在这里,“extern C”是一个完全无关紧要的东西,与此毫不相关。 - Omnifarious