在C语言中读写结构体

8

我知道在C语言中,结构体的内存布局可能与代码中不同。例如:

struct a {
     short x;
     int y;
};

假设使用2字节的short和4字节的int,编译器希望将成员对齐到4字节边界上,因此在内存中实际上可能需要8个字节...所以x和y之间有2个字节的空隙。
这使得在语言、编译器和硬件之间读写结构体不具备可移植性。唯一的方法是逐个成员进行读写。是的,字节序也是一个问题,必须在成员级别上进行交换,但假设这不是一个问题。
Fortran中有一个“sequence”限定符用于派生类型(结构体),告诉编译器按照给定的方式在内存中布置成员。这允许对派生类型进行可移植的读写。
我的问题是:是否有任何方法在C中以可移植(且易于维护)的方式执行类似的操作?

1
大多数编译器都有指令来压缩结构体,因此它可以在成员之间具有其他(或没有)填充。快速搜索应该可以帮助您找到所需的编译器信息。然而,字节序问题不容易克服,但如果您只针对单个硬件平台,则应该没问题。 - Some programmer dude
@JoachimPileborg 或者你可以像这样添加一个BOM,比如 int 0xffffeeff,并根据ee的位置在输入时进行字节交换。 - ratchet freak
将对象序列化和反序列化为文本,避免任何与字节序、打包和可移植性有关的问题。 - edc65
Fortran的序列类型概念基本上不允许“可移植读写派生类型”。在不同的编译器和不同的平台(以及同一平台上的不同编译器选项)之间,仍然可能存在兼容性问题。 - IanH
2个回答

5
是的,Fortran 2003引入了bind(C)说明符,它告诉编译器执行与关联的C编译器完全相同的操作。
type, bind(C) :: a
  components
end type

此外,iso_c_binding 模块(Fortran 2003 C 互操作性的一个子模块)定义了常量来帮助您连接 C 和 Fortran 内部类型:
use intrinsic :: iso_c_binding, only: c_short, c_int

type, bind(C) :: a
  integer(c_short) :: x
  integer(c_int) :: y
end type

这种类型被称为与您的C结构体“互操作”。

C互操作性在编译器中得到了广泛支持。很难找到一个不支持此功能但仍由其供应商支持的编译器。

在混合使用C和Fortran时,避免使用sequence。根据标准,序列类型无法实现互操作性。


2

我不知道有没有100%干净的解决方案。通常我会创建两个名为pack_on.hpack_off.h的头文件。要定义一个没有填充的结构:

#include <pack_on.h>

struct a {
    short x;
    int y;
} PACKED_STRUCTURE;

#include <pack_off.h>
文件包含预编译指令或编译器所需的内容,例如:

文件包含预编译指令或编译器所需的内容,例如:

#ifdef _MSC_VER
#pragma pack(1)
#define PACKED_STRUCTURE /* nothing */
#endif

#ifdef __GNUC__
#define PACKED_STRUCTURE __attribute__(packed)
#endif

正如你所说,当二进制布局成为问题时,字节序通常也很重要。但是上述解决方案并没有涉及这个问题。
请注意,在整个代码中使用紧凑结构体也是不明智的。当您不强制使用这些紧凑结构时,编译器添加的填充对于快速内存访问非常有用。
在许多应用程序中,最好仅在处理读写这些紧凑结构的一个层次中使用这些紧凑结构,并将它们复制到正确对齐的结构中,然后将其传递到应用程序核心进行进一步处理。

在解包期间,您还可以执行字节交换以处理字节序。 - ratchet freak

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