【问题标题】:C struct/union with explicit (manual) field layout?具有显式(手动)字段布局的 C 结构/联合?
【发布时间】:2011-11-03 06:42:02
【问题描述】:

此 C# 代码的 C 等效项是什么?

[StructLayout(LayoutKind.Explicit)]
struct Test
{
    [FieldOffset(0)] int a;  // Integer at byte offset 0
    [FieldOffset(1)] int b;  // Integer at byte offset 1
    [FieldOffset(3)] int c;  // Integer at byte offset 3
};

(我不在乎它是否不可移植,例如int被假定为4字节等)

【问题讨论】:

标签: c# c visual-c++


【解决方案1】:

这似乎在 Visual Studio 下运行良好:

#pragma pack(push)

#pragma pack(1)
typedef union
{
    int a;
    struct
    {
        char unused0;
        int b;
    };
    struct
    {
        char unused1;
        char unused2;
        char unused3;
        int c;
    };
} Test;

#pragma pack(pop)

【讨论】:

  • 但显然您对这些特定解决方案都不满意。另一方面,这应该符合您的期望吧?直接访问按指定对齐的int 成员。你还能期待什么?
  • 杰夫,我认为这个问题没有准确提出。你和我都认为他在问如何构建具有显式布局的 C 数据结构,但显然情况并非如此。
  • @Jeff:我很抱歉——我没有注意到您的结构是匿名的,并且这些字段是可以直接访问的。它不太理想,但绝对是一个可用的答案,并且与其他答案不同,+1;谢谢!
【解决方案2】:

这是一个可以跨 clang、gcc 和 msvc 工作的宏(尚未测试 msvc 版本):

#define YDUMMY(suffix, size) char dummy##suffix[size]
#define XDUMMY(suffix, size) YDUMMY(suffix, size)
#define DUMMY(size) XDUMMY(__COUNTER__, size)
#ifdef __GNUC__
#define EXPLICIT_UNION_START(name) union Test {
#define EXPLICIT_UNION_END() };
#define EXPLICIT_OFFSET_FIELD(foff, ftype, fname) struct __attribute__((packed)) { DUMMY(foff); ftype fname; };
#elif defined(_MSC_VER)
#define EXPLICIT_UNION_START(name) #pragma pack(push, 1) \
union Test {
#define EXPLICIT_UNION_END() }; \
#pragma pack(pop)
#define EXPLICIT_OFFSET_FIELD(foff, ftype, fname) struct { DUMMY(foff); ftype fname; };
#else
#error "What compiler is this?"
#endif

EXPLICIT_UNION_START(Test)
    EXPLICIT_OFFSET_FIELD(0, int, a)
    EXPLICIT_OFFSET_FIELD(1, int, b)
    EXPLICIT_OFFSET_FIELD(3, int, c)
EXPLICIT_UNION_END()

感谢未命名的字段,访问您定义的字段的语法不会被虚拟名称污染:

int main() {
    union Test t;
    t.b = 13;
    printf("offset a = %zx\n", offsetof(union Test, a));
    printf("offset b = %zx\n", offsetof(union Test, b));
    printf("offset c = %zx\n", offsetof(union Test, c));
    printf("t.b = %d\n", t.b);
    return 0;
}

【讨论】:

    【解决方案3】:

    这个怎么样。

    union u_t {
        struct {
            int V;
        } a;
        struct {
            byte dummy;
            int V;
        } b;
        struct {
            byte dummy1;
            byte dummy2;
            byte dummy3;
            int V;
        } c;
    };
    

    虚拟字段用于强制偏移。我认为某些编译器可以强制字段或结构对齐,因此您需要确保在编译时该选项已关闭。请参阅pragma pack directive。获取 a、b、c 值的方法是引用联合中每个相应结构内的 V 字段。例如,如果 u 是 u_t 类型,那么

      u.c.V = 17;
    

    【讨论】:

    • 那行不通,因为我不能像整数一样读/写c
    • 然后将名称更改为 struct1 struct2 struct3。正如我最初在回答中所说,您使用u.c.V 引用c 值。 V 是一个整数。如果您担心符号名称,请更改它们。
    • 所以我的理解是“在 Visual C++ 中没有直接的翻译”——对吗?
    • 嗯,我不明白这个问题。当我阅读“什么是等价物”时,我以为您正在寻找具有相似布局的数据结构,这将允许您以不同的偏移量引用指向同一内存块的不同 4 字节 int 值。 C 联盟就是这样做的,而我建议的联盟就是这样做的。您似乎也有这样的想法,即您想要完全相同的语法和语义。我认为没有答案。 C有不同的语法。你过度约束了这个问题。你需要决定你真正想要什么。
    • 也许您要问的是,如何在 C++ 中使用托管扩展来执行此操作?在这种情况下,它的语法基本相同。见这里:stackoverflow.com/q/5375370/48082
    【解决方案4】:

    你可以用一个联合来做到这一点,也许用一些#defines来简化使用。

    union Test {
        struct {
            int V_a;
        } s_a;
        struct {
            char V_a;
            int V_b;
        } S_b;
        struct {
            char V_a;
            short V_b;
            int V_c;
        } S_c;
    
    #define a S_a.V_a
    #define b S_b.V_b
    #define c S_c.V_c
    };
    

    【讨论】:

      【解决方案5】:

      不完全是你要求的,但你应该可以使用bit fields达到类似的效果。

      另外,对于 Visual C++,可能值得一看:__declspec(align(#))#pragma pack

      【讨论】:

      • 位域会将整数视为 4 个字节还是 1 个字节?
      • @Mehrdad 它们将被视为声明的宽度。例如,如果您有一个unsigned int x : 3,并尝试为其分配 8,则该字段实际上会因为环绕而变为 0(3 位可以表示从 0 到 7 的数字)。但是,没有什么可以阻止您将字段声明为 : 8: 32 位宽(分别为 1 和 4 个字节)。您还可以在联合中包装位字段,或在位级别操作它们,因此您可以实现几乎任何可以想象的行为......
      【解决方案6】:

      使用联合的替代方法是使用方法来访问值,无论如何您可能都应该这样做。虽然它是 C++,但您要求使用 C,但根据您对 VC++ 的使用,我假设 C++ 没问题。

      #ifdef __cplusplus
      struct Test {
          int a() {
              return *(int*)&values_[0];
          }
          void a(int value) {
              *(int*)&values_[1] = value;
          }
          int b() {
              return *(int*)&values_[1];
          }
          void b(int value) {
              *(int*)&values_[1] = value;
          }
          int c() {
              return *(int*)&values_[3];
          }
          void c(int value) {
              *(int*)&values_[3] = value;
          }
      
      private:
          char[8] values_;
      };
      #endif
      

      【讨论】:

      • 我从来没有完全理解过这个主题,但这不违反严格的别名规则吗?
      • 确实如此,但对于问题的主题并不重要,因为他不关心可移植性等。如果你想让它更干净一些,你可以用位移运算符替换那些取消引用单独设置每个字节,(但以性能为代价,可能可以忽略不计。)
      猜你喜欢
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 2013-06-09
      • 1970-01-01
      • 2013-02-24
      • 1970-01-01
      • 1970-01-01
      • 2014-01-26
      相关资源
      最近更新 更多