c++ 如何更改结构体的字节顺序?

ahy6op9u  于 2023-05-08  发布在  其他
关注(0)|答案(4)|浏览(163)

如何在C或C++中更改打包结构的顺序?

struct myStruct {
  uint32_t A;
  uint16_t B1;
  uint16_t B2;
} __attribute__((packed));

结构(或LSB)的地址0x 0为A
我的应用程序与硬件通信,硬件中的结构定义如下:

struct packed {
  logic [31:0] A;
  logic [15:0] B1;
  logic [15:0] B2;
} myStruct;

但在SystemVerilog中,“地址0x 0”或更准确地说,结构的LSB是LSB of B2 = B2[0]
顺序颠倒了。
为了保持一致性并避免更改硬件部分,我想反转整个C/C++结构的“endianness”。
我可以把所有的字段取反:

struct myStruct {
  uint16_t B2;
  uint16_t B1;
  uint32_t A;
} __attribute__((packed));

但它容易出错而且不太方便。
对于数据类型,SystemVerilog和Intel CPU都是little-endian,这不是问题。

  • 我该怎么做?
y0u0uwnf

y0u0uwnf1#

如何更改结构体的字节顺序?
不能更改成员中的字节顺序。并且不能将成员相对于其他成员的内存顺序更改为与其声明的顺序不同。
但是,您可以更改成员的声明顺序,这决定了它们的内存顺序。第一个成员总是在最低的内存位置,第二个成员在最低的内存位置之后,依此类推。
如果基于verilog源代码可以知道成员的正确顺序,那么理想情况下,C结构定义应该使用元编程来生成,以确保匹配顺序。
很容易出错
依赖于特定的内存顺序确实容易出错。
可以只依赖于源数据(假设是一个字节数组)的已知内存顺序,而根本不依赖于成员的内存顺序:

unsigned char* data = read_hardware();
myStruct s;
s.B2 = data[0] << 0u
     | data[1] << 8u;
s.B1 = data[2] << 0u
     | data[3] << 8u;
s.A  = data[4] << 0u
     | data[5] << 8u
     | data[6] << 16u
     | data[7] << 24u;

这既不依赖于成员的内存布局,也不依赖于CPU的字节序。它仅依赖于源数据的顺序(在本例中假定为小端)。
如果可能的话,这个函数最好也基于verilog源代码生成。

wj8zmpe1

wj8zmpe12#

如何在C或C中更改打包结构的顺序?
C指定结构体的成员在内存中按声明的顺序排列,当转换为适当的指针类型时,first-声明的地址等于整个结构体的地址。至少对于C中可表达的结构类型,比如你的,符合C
的实现将遵循相同的成员顺序规则。那些支持打包结构布局作为扩展的实现在它们的意思上是非常一致的:打包结构布局在成员之间没有填充,总体大小是成员大小之和。没有其他影响。
我不知道有任何实现提供了一个扩展,允许成员的顺序不同于声明顺序,谁会费心去实现它呢?成员的顺序是明确定义的。如果您想要不同的顺序,那么解决方案是更改成员的声明顺序。
如果VeriLog确实以不同的方式命令成员(对此我不能说),那么我认为你只需要与之和平相处。在你需要的时候实施它,或者以其他最有意义的方式实施它,记录双方,然后继续前进。我倾向于认为,注意到两种语言中声明顺序不同的人的数量将非常少。只要有适当的文档,那些注意到的人就不会倾向于认为有错误。

5lhxktic

5lhxktic3#

你知道,我刚刚看了AMD在它的开源驱动程序来处理endianness。
首先,他们使用cmake检测系统是否是大端/小端。

#if !defined (__GFX10_GB_REG_H__)
#define __GFX10_GB_REG_H__

/*
*    gfx10_gb_reg.h
*
*    Register Spec Release:  1.0
*
*/

//
// Make sure the necessary endian defines are there.
//
#if defined(LITTLEENDIAN_CPU)
#elif defined(BIGENDIAN_CPU)
#else
#error "BIGENDIAN_CPU or LITTLEENDIAN_CPU must be defined"
#endif

union GB_ADDR_CONFIG
{
    struct
    {
#if defined(LITTLEENDIAN_CPU)
        unsigned int                       NUM_PIPES : 3;
        unsigned int            PIPE_INTERLEAVE_SIZE : 3;
        unsigned int            MAX_COMPRESSED_FRAGS : 2;
        unsigned int                       NUM_PKRS  : 3;
        unsigned int                                 : 21;
#elif defined(BIGENDIAN_CPU)
        unsigned int                                 : 21;
        unsigned int                       NUM_PKRS  : 3;
        unsigned int            MAX_COMPRESSED_FRAGS : 2;
        unsigned int            PIPE_INTERLEAVE_SIZE : 3;
        unsigned int                       NUM_PIPES : 3;
#endif
    } bitfields, bits;
    unsigned int    u32All;
    int             i32All;
    float           f32All;
};

#endif

是的,有一些代码重复,如上所述。但我也不知道一个普遍更好的解决方案。

hfsqlsce

hfsqlsce4#

与端序问题无关,我不建议将C位字段用于这种目的,或者用于任何实际上需要显式控制位对齐的目的。很久以前,将性能置于可移植性之上的决定破坏了这种可能性。在C中,位字段(以及一般意义上的结构)的对齐没有很好地定义,这使得位字段在许多情况下都是无用的。IMO最好让程序员做出这样的优化决定,或者实现一个严格可移植的(非机器依赖的)打包关键字。如果这意味着编译器不得不偶尔发出组合多个shift-and操作的代码,那就这样吧。
据我所知,这种事情的唯一通用解决方案是添加一个层,该层显式地使用移位与逻辑实现位字段。当然,这可能会破坏性能,因为您确实希望在编译时处理条件,这很讽刺,因为性能是这种情况的首要原因。

相关问题