C++类或结构体与C结构体的兼容性问题

7

有没有可能编写一个C++类或结构体,使其与C结构体完全兼容?在这里的兼容性指对象的大小和变量的内存位置。我知道使用*(point*)&pnt甚至(float*)&pnt(在不同的情况下变量是浮点数)是不好的,但考虑到性能的原因,这确实是必要的。每秒使用常规类型转换运算符一百万次是不合理的。

以这个例子为例

Class Point {
    long x,y;
    Point(long x, long y) {
        this->x=x;
        this->y=y;
    }

    float Distance(Point &point) {
        return ....;
    }
};

C语言版本是一个POD结构体。

struct point {
    long x,y;
};

你想要实现这种兼容性的目的是什么?你是想从一个C++程序中传递二进制数据到一个C程序,还是想将一个C++对象传递给一个C函数? - Dima
4
请问需要翻译的内容是:“但要考虑到这对性能而言确实是必需的。每秒钟使用普通类型转换运算符一百万次是不合逻辑的。” - GManNickG
@Dima:转换为C函数@GMan:非邪恶的方法应该定义类型转换运算符 operator point() { point p; p.x=this->x; p.y=this.y; return p; }然而,我将在两种情况下使用这些点,一种是int类型,用于传递Windows函数以查找光标位置。另一种是float类型,用于OpenGL作为顶点(同时将其转换为float数组),第一个每帧执行一次(350次/秒),另一个是4个对象绘制x 350。它们将在游戏引擎中使用。 - Cem Kalyoncu
7个回答

21

最清晰的方法是继承自C结构体:

struct point
{
    long x, y;
};

class Point : public struct point
{
  public:
    Point(long x, long y)
        {    this->x=x; this->y=y; }

    float Distance(Point &point)
        {                return ....;        }
}

C++编译器保证了C风格的struct point与C编译器具有相同的布局。C++类Point继承了它的基类部分的这种布局(并且由于它不添加数据或虚成员,因此它将具有相同的布局)。指向类Point的指针将被转换为指向struct point的指针而不需要强制转换,因为转换为基类指针始终是受支持的。因此,您可以使用class Point对象,并自由地将指向它们的指针传递给期望指向struct point的指针的C函数。

当然,如果已经有一个定义struct point的C头文件,则可以直接包含它,而无需重复定义。


这就是我发现的。谢谢! - eonil
这可能导致可怕的混乱,因为结构体成员是公开的。这基本上完全破坏了任何封装。 - Fake Name
1
@FakeName 当涉及到C语言时,这是固有的问题,请参见https://dev59.com/EXE85IYBdhLWcg3w9orw。此外,如果您在继承中使用`protected`而不是`public`,则至少可以在C++ Point中获得封装性。另请参见https://isocpp.org/wiki/faq/mixing-c-and-cpp#cpp-objs-passed-to-c。 - Antonio

10

是的。

  • 在两种语言中使用相同的类型及顺序
  • 确保类中没有任何虚函数(以避免在前面占用vtable指针)
  • 根据所使用的编译器,可能需要通过pragma调整结构体的打包方式,以保证兼容性。

(编辑)

  • 此外,还需注意使用编译器检查类型的sizeof()。例如,我曾遇到一款编译器将short作为32位值存储(而大多数编译器通常使用16位)。更常见的情况是,在32位架构上,int通常是32位,在64位架构上则是64位。

1
根据我的经验,pack pragma 不是一个好主意。如果你按照自然对齐的方式排序成员变量,那么 pack pragma 就不是必需的。如果你没有按照自然对齐的方式排序它们并尝试使用 pragma 来修复它,你可能会遇到两个问题:1)pragma 仍然无法工作;2)你将强制编译器使某些处理器不想错位的东西错位,例如浮点数。至少在 PowerPC 上,错位的浮点数将生成一个由软件处理的中断,这可能会显著影响性能。 - KeyserSoze
使用与C结构相同的布局定义C++对象是可行的,但它并不能像从结构体中派生类那样清晰地表达意图(请参见下面我的回答)。 - Stephen C. Steel
@keysersoze:是的,自然对齐更好。但是,一些编译器仍会以不同的方式打包数据,这就是提到打包很有用的地方。确实,我忘记了提到注意成员大小。我将编辑我的帖子以添加该点。 - Jason Williams
你要找的词是POD类型。它们在标准中有明确定义,基本上它们的整个目的就是与C兼容。 - jalf
@jalf:是的,我认为重复keysersoze关于POD的好帖子毫无意义。我只是想说(在一般情况下)你所需要验证的就是你正在使用的特定编译器是否生成相同的二进制结构。不管我使用什么类型,我都会测试我的代码以验证这一点。 - Jason Williams
刚注意到你的关于sizeof()的评论... 你知道stdint.h吗?http://en.wikipedia.org/wiki/Stdint.h "无论编译器是否符合C99标准,C和C++开发人员都应该知道更新他们的编码标准是很重要的,因为可以下载或快速创建一个版本的stdint.h(用于C),以及一个版本的stdint.h和cstdint(用于C++)"。 - KeyserSoze

5

POD适用于C ++。您可以拥有成员函数。“在C ++中,POD类型是仅包含POD类型作为成员的聚合类,没有用户定义的析构函数,没有用户定义的复制赋值运算符和没有非静态成员指针类型”

您应该设计您的POD数据结构,使它们具有自然对齐方式,然后它们可以在由不同编译器创建的不同架构的程序之间传递。自然对齐是指任何成员的内存偏移量可被该成员的大小整除。即:float位于可被4整除的地址上,double位于可被8整除的地址上。如果您声明一个char后跟一个float,则大多数架构将填充3个字节,但某些架构可能会填充1个字节。如果您声明一个float后跟一个char,则所有编译器(我应该添加此声明的来源,抱歉)都不会进行填充。


编译器可能会插入额外的填充,所以除非使用pack pragma或类似方法,否则不能确保它能正常工作。 - jalf
我从未见过编译器为具有自然对齐的结构插入填充。VC++ 6.0、2003、2005、2008,Metrowerks for PPC,Metrowerks for HC12,gcc for ARM,gcc 2.98-3.x for PowerPC,gcc 3.x-4.x for x86,SDCC for 8051,Crossworks for MSP430。如果有反例或者好的理由,请告知,不胜感激。 - KeyserSoze
如果在没有自然对齐的结构体上使用pack pragma,则通常会产生有害的副作用。例如,在PowerPC上具有不正确对齐的浮点数,将会发生硬件异常。硬件处理程序可以选择重置处理器(在嵌入式应用中很常见),或在软件中处理问题(这非常慢)。在任何情况下,使用pack pragma 只会对你造成伤害,你肯定要定义结构体具有自然对齐,并且不使用pack,以便它在各个平台上都能正常工作。 - KeyserSoze

1

C和C++是不同的语言,但C++一直以来的意图是可以有一个支持两种语言二进制兼容的实现。因为它们是不同的语言,所以这是否被支持始终是编译器实现的细节。通常,供应C和C++编译器(或具有两种模式的单个编译器)的供应商确实支持在C++代码和C代码之间传递POD-structs(和指向POD-structs的指针)的完全兼容性。

通常情况下,仅仅拥有用户定义的构造函数就会破坏这个保证,尽管有时你可以将这样一个对象的指针传递给期望指向具有相同数据结构的struct的C函数,并且它会工作。

简而言之:请查阅您的编译器文档。


1

在C和C++中使用相同的“struct”。如果您想在C++实现中添加方法,可以继承结构体,并且只要不添加数据成员或虚函数,大小应该是相同的。

请注意,如果您有一个空结构体或数据成员为空结构体,则它们在C和C++中的大小不同。在C中,sizeof(empty-struct) == 0,尽管在C99中,不应允许使用空结构体(但可能仍然作为“编译器扩展”支持)。在C ++中,sizeof(empty-struct)!= 0(典型值为1)。


0
除了其他答案之外,我会确保不要在您的C++类/结构中放置任何访问说明符(public:,private:等)。如果我没记错的话,编译器允许根据可见性重新排序成员变量块,因此private: int a; pubic: int b;可能会交换a和b。请参见例如此链接:http://www.embedded.com/design/218600150?printable=true
我承认对为什么POD的定义不包括禁止这种情况感到困惑。

-2
只要你的类没有展示出一些高级特性,比如增长虚拟物品,它应该基本上是相同的结构。
此外,你可以将无效的大写字母“Class”更改为“struct”,而不会造成任何伤害。除了成员现在将变为公共的(它们现在是私有的)。
但是现在我想到你谈论类型转换... 没有办法通过强制转换指针类型将float转换为表示相同值的long或反之亦然。我希望你只是想要这些指针来移动东西。

1
当你将结构体改为类时,唯一的影响是默认保护级别(在第一个显式 public:、private: 或 protected: 之前)从 private 变为 public。 - bdonlan
bdonlan,是的,我提到了。但你不应该低估这笔交易的情感方面;-) - Michael Krelin - hacker
浮点型版本是完全不同的情况(OpenGL),而int是Windows中的(point struct)。类示例应该在顶部有public:。我只是将其作为一个示例。 - Cem Kalyoncu
好的,我只是想确保你不是试图以这种方式转换浮点数。 - Michael Krelin - hacker

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