使用offsetof
进行指针运算时,是否定义了一个结构地址的行为,向其添加成员的偏移量,然后取消引用该地址以获取底层成员?
考虑以下示例:
#include <stddef.h>
#include <stdio.h>
typedef struct {
const char* a;
const char* b;
} A;
int main() {
A test[3] = {
{.a = "Hello", .b = "there."},
{.a = "How are", .b = "you?"},
{.a = "I\'m", .b = "fine."}};
for (size_t i = 0; i < 3; ++i) {
char* ptr = (char*) &test[i];
ptr += offsetof(A, b);
printf("%s\n", *(char**)ptr);
}
}
这应该打印&#34;那里。&#34;,&#34;你?&#34;并且&#34;很好。&#34;连续三行,目前与clang和gcc一起使用,因为您可以在wandbox上验证自己。但是,我不确定这些指针强制转换和算术是否违反了某些会导致行为未定义的规则。
答案 0 :(得分:1)
据我所知,这是明确定义的行为。但这只是因为您通过char
类型访问数据。如果您使用了其他指针类型来访问结构,那么它将是一个严格的别名违规&#34;。
严格来说,访问数组越界没有明确定义,但 定义良好,使用字符类型指针从结构中获取任何字节。通过使用offsetof
,您可以保证此字节不是填充字节(这可能意味着您将获得不确定的值)。
但请注意,丢弃const
限定符会导致行为定义不明确。
修改
类似地,强制转换(char**)ptr
是无效的指针转换 - 仅此一项是未定义的行为,因为它违反了严格的别名。变量ptr
本身被声明为char*
,因此您无法对编译器撒谎并说出&#34;嘿,这实际上是char**
&#34; ,因为它不是。这与ptr
指向的内容无关。
我相信没有定义不明确的行为的正确代码就是这样:
#include <stddef.h>
#include <stdio.h>
#include <string.h>
typedef struct {
const char* a;
const char* b;
} A;
int main() {
A test[3] = {
{.a = "Hello", .b = "there."},
{.a = "How are", .b = "you?"},
{.a = "I\'m", .b = "fine."}};
for (size_t i = 0; i < 3; ++i) {
const char* ptr = (const char*) &test[i];
ptr += offsetof(A, b);
/* Extract the const char* from the address that ptr points at,
and store it inside ptr itself: */
memmove(&ptr, ptr, sizeof(const char*));
printf("%s\n", ptr);
}
}
答案 1 :(得分:0)
鉴于
struct foo {int x, y;} s;
void write_int(int *p, int value) { *p = value; }
标准中没有任何内容可以区分:
write_int(&s.y);
和
write_int((int*)(((char*)&s)+offsetof(struct foo,y)), 12);
标准的读取方式可能意味着上述两种情况都违反了左值类型规则,因为它没有指定可以使用成员类型的左值访问结构的存储值,这需要希望作为结构成员访问的代码写为:
void write_int(int *p, int value) { memcpy(p, value, sizeof value); }
我个人认为这是荒谬的;如果&s.y
无法用于访问。{
类型为int
的左值,为什么&
运算符会产生int*
?
另一方面,我也不认为标准所说的重要。无论是clang还是gcc都不能依赖于正确处理任何事情的代码&#34;有趣的&#34;使用指针,即使在标准明确定义的情况下,除非使用-fno-strict-aliasing
调用。做出任何善意努力的编译器,以避免任何不正确的别名和#34;优化&#34;在至少一些似乎合理的标准读数下定义的情况下,在使用指针(或从其派生的其他指针)完成的所有访问都在之前的情况下处理使用offsetof
的代码时没有问题。通过其他方式下一次访问该对象。