对 C 结构中两个连续指针的包装做出假设是否安全?
Is it safe to make assumptions about the packing of two consectutive pointers in a C struct?
我正在研究从基于 GObject 的库到标准 ML 的语言绑定。更准确地说,我实现了对 G(S)List 集合类型的支持。此实现需要从 G(S)Lists links 中提取数据并从标准 ML 中获取下一个 link。而不是通过 FFI 调用 G(S)List 函数之一,我希望可以使用 FFI 的指针操作函数来访问适当的结构元素以提高效率。但是,这要求可以预测结构中第二个指针的偏移量,即下一个 link 指针。作为参考,这就是 GSList link 结构的样子
struct GSList {
gpointer data;
GSList *next;
};
我认为这对任何人来说都不足为奇。可以安全地假设指向结构的指针指向它的第一个元素,即数据指针,但是第二个元素呢?我可以对第二个元素相对于第一个元素的偏移量做出任何与平台无关的假设吗?
不,一般来说,你不能做出这样的假设。您的结构的第二个成员可能有特定的对齐要求,这将导致在结构布局中插入填充字节。
有一种特殊情况:如果第二个成员与第一个成员具有相同的类型,则假设没有这样的填充似乎是合理的,但标准为编译器留下了额外的灵活性,因此无法保证。
这种特殊情况在这里并不适用,因为 gpointer
可能是 void *
指针的类型定义。在某些架构上(罕见且正在消失,但 Cray 曾经有过),指向不同类型的指针可能具有不同的表示形式,因此具有不同的对齐要求。
It's safe to assume that the pointer to a struct points to it's first
element, ie the data pointer, but how about the second element?
是的。这是安全的。 struct
对象的地址和它的第一个元素的地址保证是相同的。 在结构的第一个成员之前不允许填充。
Can I make any platform independent assumptions about the offset of
the second element in relation to the first?
没有。这不安全。结构的第一个和第二个元素之间可能有填充字节,任何假设都是不可移植的。但你可以使用
offsetof
从结构的开头获取 any 成员的偏移量。
例如,您可以将 next
的偏移量设置为:
size_t next_offset = offsetof(struct GSList, next);
GCC 提供了一个 attribute 来禁用填充:
__attribute__((packed))
MSVC 也提供了类似的选项。
正如其他人所说,该标准甚至要求 struct
在第一个成员之前没有填充。
对于其他成员来说,这是不安全的。但是,您可以使用 offsetof
来获取偏移量。
实际上,Linux 内核使用 container_of
宏从其中一个成员的地址获取结构的基地址。请注意,这不会使用内核的其他功能,因此它也可以按原样在您的代码中使用。:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
struct S {
int a;
int b;
};
int *ip; // assume points to the `b` member in an object of struct S
// get address of the struct S
struct S *sp = container_of(ip, struct S, b);
按照定义,它使用 gcc 扩展来添加一些类型安全性(请参阅 link)。可以通过移除安全措施来采用不同的工具链。
我正在研究从基于 GObject 的库到标准 ML 的语言绑定。更准确地说,我实现了对 G(S)List 集合类型的支持。此实现需要从 G(S)Lists links 中提取数据并从标准 ML 中获取下一个 link。而不是通过 FFI 调用 G(S)List 函数之一,我希望可以使用 FFI 的指针操作函数来访问适当的结构元素以提高效率。但是,这要求可以预测结构中第二个指针的偏移量,即下一个 link 指针。作为参考,这就是 GSList link 结构的样子
struct GSList {
gpointer data;
GSList *next;
};
我认为这对任何人来说都不足为奇。可以安全地假设指向结构的指针指向它的第一个元素,即数据指针,但是第二个元素呢?我可以对第二个元素相对于第一个元素的偏移量做出任何与平台无关的假设吗?
不,一般来说,你不能做出这样的假设。您的结构的第二个成员可能有特定的对齐要求,这将导致在结构布局中插入填充字节。
有一种特殊情况:如果第二个成员与第一个成员具有相同的类型,则假设没有这样的填充似乎是合理的,但标准为编译器留下了额外的灵活性,因此无法保证。
这种特殊情况在这里并不适用,因为 gpointer
可能是 void *
指针的类型定义。在某些架构上(罕见且正在消失,但 Cray 曾经有过),指向不同类型的指针可能具有不同的表示形式,因此具有不同的对齐要求。
It's safe to assume that the pointer to a struct points to it's first element, ie the data pointer, but how about the second element?
是的。这是安全的。 struct
对象的地址和它的第一个元素的地址保证是相同的。 在结构的第一个成员之前不允许填充。
Can I make any platform independent assumptions about the offset of the second element in relation to the first?
没有。这不安全。结构的第一个和第二个元素之间可能有填充字节,任何假设都是不可移植的。但你可以使用
offsetof
从结构的开头获取 any 成员的偏移量。
例如,您可以将 next
的偏移量设置为:
size_t next_offset = offsetof(struct GSList, next);
GCC 提供了一个 attribute 来禁用填充:
__attribute__((packed))
MSVC 也提供了类似的选项。
正如其他人所说,该标准甚至要求 struct
在第一个成员之前没有填充。
对于其他成员来说,这是不安全的。但是,您可以使用 offsetof
来获取偏移量。
实际上,Linux 内核使用 container_of
宏从其中一个成员的地址获取结构的基地址。请注意,这不会使用内核的其他功能,因此它也可以按原样在您的代码中使用。:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
struct S {
int a;
int b;
};
int *ip; // assume points to the `b` member in an object of struct S
// get address of the struct S
struct S *sp = container_of(ip, struct S, b);
按照定义,它使用 gcc 扩展来添加一些类型安全性(请参阅 link)。可以通过移除安全措施来采用不同的工具链。