Legit使用C / C ++中的offsetof offset

时间:2009-09-21 08:20:31

标签: c++ c macros

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问题是迄今为止我见过的最好的问题。

5 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:5)

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

关于'邪恶',你必须记住,传统上在'C'编程中完成的许多东西,特别是在资源有限的平台上,从现代计算的豪华环境来看,现在看起来像邪恶的hackery。

答案 2 :(得分:4)

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

这样的工作方式如下:需要选项的命令定义了一个包含其选项的本地结构,然后使用offsetof来指示生成GUI的代码的结构,以指示字段的位置。使用偏移而不是绝对地址允许GUI代码与结构的任何实例一起使用,而不仅仅是一个。

在一个例子中我很难快速绘制(我试过),但由于注释表明一个例子是有序的,我会再试一次。

假设我们有一个自包含的模块,称为“命令”,它在应用程序中实现了一些操作。此命令有许多控制其一般行为的选项,应通过图形用户界面向用户公开。出于该示例的目的,假设应用程序是文件管理器,并且命令可以是例如。 “复制”。

这个想法是复制代码存在于一个C文件中,而GUI代码存在于另一个C文件中,并且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. */

然后,copy命令使用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);
}

然后,假设用户要求设置复制命令的选项。然后我们可以先复制当前选项,以支持取消,并向G​​UI模块询问一个控件,该控件在运行时构建,适合编辑此命令的选项:

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模块,那么就不可能灵活得多。

答案 3 :(得分:2)

基本上,你在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));

答案 4 :(得分:1)

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”的框架都有详细记录,这种方法很好。