Legit使用C / C ++中的offsetof offset
在C / C ++中有这个宏偏移量,它允许您获取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);
这个宏有什么其他合法用途? 什么时候不应该使用这个宏?
编辑到目前为止,对于一个不同的SO问题的回答是迄今为止我见过的最好的问题。
嗯……在C中,它对于需要代码来描述数据结构的任何地方都非常有用。 我已经用它来做例如运行时生成的GUI:s用于设置选项。
这样的工作方式如下:需要选项的命令定义了一个包含其选项的本地结构,然后将该结构描述为生成GUI的代码,使用offsetof
指示字段的位置。 使用偏移而不是绝对地址允许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); }
然后,假设用户要求设置复制命令的选项。 然后,我们可以先复制当前选项,以支持取消,并向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模块,那么这将不可能灵活得多。
我在嵌入式系统中使用它的一种方法是我有一个表示非易失性存储器(例如EEPROM)布局的结构,但我不想在RAM中实际创建这个结构的实例。 您可以使用各种漂亮的宏技巧来允许您从EEPROM读取和写入特定字段,其中offsetof用于计算结构中字段地址的工作。
关于’邪恶’,你必须记住,传统上在’C’编程中完成的许多东西,特别是在资源有限的平台上,从现代计算的豪华环境来看,现在看起来像邪恶的hackery。
offsetof()
一个合法用法是确定类型的对齐方式:
#define ALIGNMENT_OF( t ) offsetof( struct { char x; t test; }, test )
需要对象的对齐可能有点低级,但无论如何我认为这是合法的用途。
基本上,你在C ++中使用指向成员( T::*
)的指针做的任何事情都是在C中使用offsetof
一个很好的选择。因此,在C ++中, offsetof
是非常罕见的。
现在这当然有点循环,所以这里有一些例子:
- 结构的半通用排序函数。
qsort
使用回调,这不是理想的。 通常,您只需按一个成员的自然顺序排序,例如结构中的第三个int
。 假设qsort_int
可以为此目的接受offsetof
参数。 - 类似地,可以编写一个宏
extract
这样你就可以说int out[10]; extract(int, &MyFoo[0], &MyFoo[10], out, offsetof(struct Foo, Bar));
int out[10]; extract(int, &MyFoo[0], &MyFoo[10], out, offsetof(struct Foo, Bar));
offsetof经常用于设备驱动程序编程,您通常必须使用普通C编写,但有时需要一些“其他”function。 考虑你有一个回调函数,它获取指向某个结构的指针。 现在这个结构本身就是另一个更大的“外部”结构的成员。 使用“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”的框架被很好地记录下来,这很好。