【问题标题】:Legit Uses of the offsetof Macro in C / C++C/C++ 中 offsetof 宏的合法使用
【发布时间】:2009-09-21 08:20:31
【问题描述】:

在 C/C++ 中有这个宏 offsetof,它允许您获取 POD 结构中成员的地址偏移量。以C FAQ 为例:

struct foo {
int a;
int b;
};

struct foo;

/* Set the b member of foo indirectly */
*(int *)((char *)foo + offsetof(b)) = 0xDEADBEEF;

现在这对我来说似乎很邪恶,我看不出这个宏有很多合法用途。

我见过的一个合法示例是它在 Linux 内核的 container_of 宏中用于获取嵌入式结构父对象的地址:

/* get the address of the cmos device struct in which the cdev
   structure which inode points to is embedded */
struct cmos_dev *cmos_devp = 
     container_of(inode->i_cdev, struct cmos_dev, cdev);

此宏还有哪些其他合法用途?你什么时候应该使用这个宏?

编辑到目前为止,this answer 到另一个 SO 问题是我迄今为止见过的最好的问题。

【问题讨论】:

标签: c++ c macros


【解决方案1】:

我在嵌入式系统中使用它的一种方式是我有一个结构,它表示非易失性存储器(例如 EEPROM)的布局,但我不想实际创建这个结构的实例在内存中。您可以使用各种不错的宏技巧来允许您从 EEPROM 读取和写入特定字段,其中 offsetof 负责计算结构中字段的地址。

关于“邪恶”,您必须记住,许多传统上在“C”编程中完成的事情,特别是在资源有限的平台上,现在从豪华的现代计算环境来看,看起来就像是邪恶的黑客。

【讨论】:

    【解决方案2】:

    offsetof() 的一个合法用途是确定类型的对齐方式:

    #define ALIGNMENT_OF( t ) offsetof( struct { char x; t test; }, test )
    

    需要对齐对象可能有点低级,但无论如何我认为这是合法的用途。

    【讨论】:

      【解决方案3】:

      嗯……在 C 语言中,它对于任何需要代码来描述数据结构的地方都非常有用。我用过它,例如做运行时生成的 GUI:s 来设置选项。

      它的工作原理如下:需要选项的命令定义一个保存其选项的本地结构,然后将该结构描述给生成 GUI 的代码,使用 offsetof 指示字段的位置。使用偏移量而不是绝对地址允许 GUI 代码处理结构的任何实例,而不仅仅是一个。

      这有点难以在示例中快速绘制(我尝试过),但由于 cmets 表明示例是有序的,我会再试一次。

      假设我们有一个独立的模块,称为“命令”,它在应用程序中实现一些动作。这个命令有一堆选项来控制它的一般行为,这些选项应该通过图形用户界面暴露给用户。出于本示例的目的,假设应用程序是文件管理器,并且命令可以是例如“复制”。

      这个想法是复制代码存在于一个 C 文件中,而 GUI 代码存在于另一个文件中,GUI 代码不需要硬编码来“支持”复制命令的选项。相反,我们在复制文件中定义选项,如下所示:

      struct copy_options
      {
        unsigned int buffer_size;     /* Number of bytes to read/write at a time. */
        unsigned int copy_attributes; /* Attempt to copy attributes. */
        /* more, omitted */
      };
      
      static struct copy_options options; /* Actual instance holding current values. */
      

      然后,复制命令将其配置设置注册到 GUI 模块:

      void copy_register_options(GUIModule *gui)
      {
        gui_command_begin(gui, "Copy");
        gui_command_add_unsigned_int(gui, "Buffer size", offsetof(struct copy_options, buffer_size));
        gui_command_add_boolean(gui, "Copy attributes", offsetof(struct copy_options, copy_attributes));
        gui_command_end(gui);
      }
      

      然后,假设用户要求设置复制命令的选项。然后,我们可以先复制当前选项,以支持取消,然后向 GUI 模块请求一个对话框,其中包含控件,在运行时构建,适合编辑此命令的选项:

      void copy_configure(GUIModule *gui)
      {
        struct copy_options edit = options;
      
        /* Assume this opens a modal dialog, showing proper controls for editing the
         * named command's options, at the address provided. The function returns 1
         * if the user clicked "OK", 0 if the operation was cancelled.
        */
        if(gui_config_dialog(gui, "Copy", &edit))
        {
          /* GUI module changed values in here, make edit results new current. */
          options = edit;
        }
      }
      

      当然,这段代码假定设置是纯值类型,所以我们可以使用简单的结构赋值来复制结构。如果我们还支持动态字符串,我们需要一个函数来进行复制。不过,对于配置数据,任何字符串可能最好在结构中表示为静态大小的char 数组,这很好。

      请注意,GUI 模块只知道每个值的位置(以偏移量表示)这一事实如何允许我们为对话框函数提供临时的堆栈副本。如果我们改为使用指向每个字段的直接指针来设置 GUI 模块,这将是不可能的,而且灵活性会大大降低。

      【讨论】:

      • 不只是使用指针还允许您“使用结构的任何实例”吗?
      • @Robert:因为它只适用于一个;你给它一个指针的那个。通过使用偏移量,您可以为同一结构的两个不同实例构建 GUI,而无需将结构重新描述为 GUI 构建代码。
      • 由于某种原因我没有得到它 - 你能在你的帖子中添加一个简短的代码示例吗?
      • 可以举个例子吗?
      • 这里的一个问题是缺乏类型安全性。此外,描述需要大小信息以及偏移量,因为您可能不会公开所有成员。最后,offsetof 在应用于位域时可能会失败。就个人而言,我从未使用过它,也没有遇到过有用的情况,也没有其他解决方案。
      【解决方案4】:

      offsetof 相对经常用于设备驱动程序编程,您通常必须用纯 C 语言编写,但有时需要一些“其他”功能。 考虑您有一个回调函数,它获取指向某个结构的指针。 现在这个结构本身就是另一个更大的“外部”结构的成员。 使用“offsetof”,当您只有访问权限时,您可以更改“外部”结构的成员 到“内部”成员。

      类似这样的:

      struct A
      {
       int a1;
       int a2;
      };
      
      struct B
      {
       int b1;
       int b2;
       A a;
      };
      
      void some_API_callback_func(A * a)
      {
       //here you do offsetof 
       //to get access to B members
      }
      

      如果你有可能将结构 A 用作结构 B 的一部分,这当然是危险的。 但是在许多地方,“some_API_callback_func”的框架有很好的记录,这工作得很好。

      【讨论】:

      • 谢谢,但这正是我已经描述的关于 linux 内核中的 container_of 宏的内容。
      【解决方案5】:

      基本上,在 C++ 中使用指向成员 (T::*) 的指针所做的任何事情都适合在 C 中使用 offsetof。因此,offsetof 在 C++ 中非常少见。

      现在这当然有点循环,所以这里有一些例子:

      • 结构的半通用排序函数。 qsort 使用回调,这并不理想。通常你只需要按一个成员的自然顺序排序,例如结构中的第三个int。为此,假设的 qsort_int 可以接受 offsetof 参数。
      • 同样,可以写一个宏extract,这样你就可以说int out[10]; extract(int, &MyFoo[0], &MyFoo[10], out, offsetof(struct Foo, Bar));

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-08
        • 2011-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多