C/C++ 中 offsetof 宏的合法使用

2022-01-11 00:00:00 c macros c++

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

There is this macro offsetof in C/C++ which allows you to get the address offset of a member in a POD structure. For an example from the C FAQ:

struct foo {
int a;
int b;
};

struct foo;

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

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

Now this just seems evil to me and I can't see many legit uses of this macro.

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

One legit example I have seen is it's use in the container_of macro in the Linux Kernel for getting the address of an embedded structures parent object:

/* 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);

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

What other legit uses are there for this macro? When should you not use this macro?

编辑到目前为止 this answer to a different SO question是迄今为止我见过的最好的答案.

EDIT So far this answer to a different SO question is the best one I've seen so far.

推荐答案

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

Well ... In C, it's very useful for any place where you need code to describe a data structure. I've used it e.g. to do run-time-generated GUI:s for setting options.

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

This worked like this: a command that needs options defines a local structure holding its options, and then describes that structure to the code that generates the GUI, using offsetof to indicate where fields are. Using offsets rather than absolute addresses allows the GUI code to work with any instance of the struct, not just one.

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

This is bit hard to sketch quickly in an example (I tried), but since comments indicate an example is in order, I'll try again.

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

Assume we have a self-contained module, called a "command", that implements some action in the application. This command has a bunch of options that control its general behaviour, which should be exposed to the user through a graphical user interface. For the purposes of this example, assume the application is a file manager, and the command could be e.g. "Copy".

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

The idea is that the copy code lives in one C file, and the GUI code in another, and the GUI code does not need to be hard-coded to "support" the copy command's options. Instead, we define the options in the copy file, like so:

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 模块:

Then, the copy command registers its configuration settings with the GUI module:

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 模块请求一个对话框,其中包含控件,在运行时构建,适合编辑此命令的选项:

Then, let's say the user asks to set the copy command's options. We can then first copy the current options, to support cancelling, and ask the GUI module for a dialog holding controls, built at run-time, suitable for editing this command's options:

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 数组,这很好.

Of course, this code assumes the settings to be pure value-types, so we can copy the struct using simple struct assignment. If we also supported dynamic strings, we'd need a function to do the copying. For configuration data though, any string would probably best be expressed as a statically-sized char array in the struct, which would be fine.

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

Note how the fact that the GUI module only knows where each value lives expressed as an offset allows us to provide the dialog function with a temporary on-stack copy. Had we instead set up the GUI module with direct pointers to each field, this would not be possible which would be far less flexible.

相关文章