【问题标题】:GCC - Struct defining members in specific offsetsGCC - 在特定偏移量中定义成员的结构
【发布时间】:2011-11-23 08:05:42
【问题描述】:

在 gcc 中有没有办法,我可以在特定偏移量中定义具有特定成员的结构?

我想通过以下方式定义一个结构体:

struct {
.offset(0xDC) //or something equivalent
   int bar;
} foo;

然后是下面的语句:

int a = foo.bar

将与语句相同:

int a = *(int*)((char*)&foo + 0xDC);

* 更新 *

一些背景: 我想访问没有正确定义的导出结构中的成员,它有很多成员 - 我只关心其中的少数几个,并且它们的偏移量(结构原始定义)在每个目标平台上都有点不同(我需要为几个不同的平台编译我的代码)

我已经考虑过这里的 cmets 中提到的填充选项,但是每次我想添加成员时,它都需要我进行一些烦人的计算。 例如:

strcut {
.offset(0xDC)
    int bar;
.offset(0xF4)
    int moo;
}foo;

那么简单:

struct __attribute__ ((__packed__)) struct {
   char pad1[0xD8];
   int bar;
   char pad2[0x18];
   int moo;
}foo;

并且不考虑 sizeof(int) 可以从平台到平台的变化

【问题讨论】:

  • 您的意思是bit-fields 吗?
  • 我认为不,如果你真的想要恒定的偏移量,我会用强制转换和指针来做到这一点,例如*((int*)(((char*)p)+0xd8)) 可以隐藏在宏中。

标签: c++ c gcc


【解决方案1】:

你应该看看__attribute__ ((__packed__))

在你的情况下,你会写:

struct __attribute__ ((__packed__)) {
   char unused[0xDC];
   int bar;
} foo;

如果您可以解释您正在尝试做的事情,可能还有其他可能更优雅的解决方案。

【讨论】:

  • 我忘了提 packed 属性,虽然我暗示过 +1
  • 我正在尝试编写在几个不同平台上运行的代码。我想访问一个由不同模块导出的结构,而不是我自己编写的,并且我没有标题。在每个平台上,我要访问的成员位于不同的偏移量上,我希望该偏移量是每个平台的构建系统定义的宏...您的解决方案是我目前使用的,但我想知道是否有有什么更清楚的
  • @t_z 实际上,我认为这是唯一的方法,除非您将其保存到缓冲区中并从那里进行偏移,这将获得相同的结果,尽管看起来更复杂。
  • @t_z 我不认为使用结构是最好的选择;我会根据平台创建一个(或更多)函数来解析该结构。这样我可以避免做一些编译技巧,在我看来这会增加构建系统的复杂性。
【解决方案2】:

其他答案中提出的使用填充和联合的解决方案:

#include <stdio.h>

#define OFF_STRUCT(name, members) union { members } name

#define OFF_MEMB(offset, member)                 \
        struct __attribute__ ((__packed__)) {    \
                char pad[offset];                \
                member;                          \
        }

int main(int argc, char *argv[])
{
        OFF_STRUCT(foo,
                OFF_MEMB(0xD8, int bar);
                OFF_MEMB(0x18, double moo);
                OFF_MEMB(0x1, int bee);
        );

        printf("offset: 0x%x 0x%x 0x%x\n",
                (void*)&foo.bar - (void*)&foo,
                (void*)&foo.moo - (void*)&foo,
                (void*)&foo.bee - (void*)&foo
        );
}

输出:

offset: 0xd8 0x18 0x1

【讨论】:

    【解决方案3】:

    你总是可以用字节填充它,并确保你告诉 gcc 不要对齐你的结构(因为这可能会抛出偏移量)。在这种情况下,您需要像 char pad_bytes[num_pad_bytes]; 这样的成员。虽然真的有理由这样做吗?您总是可以通过一些指针算术来计算结构成员的偏移量。 注意:您可能希望使用 uint8_t 类型来填充而不是 char,因为某些编译器实际上可能会将 char(通常是一个字节)填充到一个单词的大小。

    计算偏移量很简单

    size_t offset = (size_t)&((struct_type *)0)->member);
    

    所有这一切只是简单地返回一个指针,如果该结构在内存中的 0x00 处(它永远不可能),则该成员将在 struct_type 中的位置,但由于我们使用 0 作为基数,因此偏移量只是由返回的引用& 运算符。

    【讨论】:

    • "这一切只是"得到未定义的行为。您正在取消引用一个空指针!
    • @curiousguy 实际上不,由于 & 运算符,它实际上并没有取消引用它。 C 中的通用链表实现使用这个确切的宏(在 linux 内核中也可以查找 container_of 宏)。这是您的链接,以防万一lxr.linux.no/#linux+v3.1.2/include/linux/stddef.h#L20
    • @JesusRamos,在这种情况下,您应该使用完全标准的 C99/C++98/C++11 宏 offsetof
    • "你现在可以删除你的反对票 :)" 我不能。取消引用就是取消引用,UB 就是 UB。 *pp 的解引用,如果p 不指向对象,则它是非法的
    • "这不是非法的,& 是运算符的地址,你实际上并没有使用指向的值" 我想我知道&amp; 做了什么。在获取地址之前,您有取消引用。如果取消引用的指针不指向对象,则取消引用无效。这是非常基本的:为了明确定义f(g(x)),必须明确定义g(x),并且必须明确定义f(y)(其中y 是结果g(x) 的值)。这里没有任何细微之处。
    【解决方案4】:

    我已经考虑过这里的 cmets 中提到的填充选项,但是每次我想添加成员时,它都需要我进行一些烦人的计算。

    关于烦人的计算,您可以使用您喜欢的脚本语言生成结构声明:

    struct = { 0xdc : (4, 'int bar'),
               0xf4 : (4, 'int moo') }
    
    def print_struct_decl (name, decl):
            print "struct __attribute__ ((packed)) %s {" % name
            off = 0
            i = 0;
            for o in sorted (decl.keys()):
                    print "\tchar pad%d [%d];" % (i, o - off)
                    i = i + 1
                    off = off + o + decl[o][0]
                    print "\t%s;" % decl[o][1]
            print "};"
    
    print_struct_decl ("whatever", struct)
    

    输出:

    struct __attribute__ ((packed)) whatever {
        char pad0 [220];
        int bar;
        char pad1 [20];
        int moo;
    };
    

    【讨论】:

      【解决方案5】:

      我会写一个包装指针的类:

      class whateverPtr {
          unsigned char *p;
      
      public:
          whateverPtr(void *p) : p(reinterpret_cast<unsigned char *>(p) { }
      
          uint32_t getBar() const { return read_uint32(0xdc); }
      
      private:
          uint32_t read_uint32(unsigned int offset) {
              return p[offset] |
                  (p[offset + 1] << 8) |
                  (p[offset + 2] << 16) |
                  (p[offset + 3] << 24);
          }
      };
      

      这是完全可移植的,可以在 bigendian 架构上按预期工作。有符号整数有点棘手,因为您需要正确编码。

      【讨论】:

        【解决方案6】:

        我不建议这样做;结构不适用于这种数据操作。如果您需要修改特定位置的数据,请使用正确类型的指针以这种方式进行更改。将结构添加到组合中只会增加复杂性而没有任何好处。

        【讨论】:

          【解决方案7】:

          我不知道这是否符合你对 gcc 的需求,但我一直在使用这个解决方案,它与我的 VS 编译器配合得很好:

          #define STR_MERGE_IMPL(a, b) a##b
          #define STR_MERGE(a, b) STR_MERGE_IMPL(a, b)
          #define MAKE_PAD(size) STR_MERGE(_pad, __COUNTER__)[size]
          #define DEFINE_MEMBER_N(type, name, offset) struct {unsigned char MAKE_PAD(offset); type name;}
          
          struct Foo
          {
              union {
                  DEFINE_MEMBER_N(int, bar, 0x8);
              };
          };
          
          Foo foo;
          foo.bar++;
          

          感谢 Can1357

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-03-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多