c语言 - 是否明确定义了通过偏移量访问成员?



当使用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("%sn", *(char**)ptr);
}
}

这应该在三行上连续打印"there.","you?"和"fine.",它目前在clang和gcc上都是这样做的,因为你可以在wandbox上验证自己。但是,我不确定这些指针转换和算术中的任何一个是否违反了某些规则,从而导致行为变得未定义。

据我所知,这是定义明确的行为。但这只是因为您通过char类型访问数据。如果您使用其他指针类型来访问该结构,那将是"严格锯齿冲突"。

严格来说,越界访问数组不是明确定义的,但使用字符类型指针从结构中获取任何字节是明确定义的。通过使用offsetof可以保证此字节不是填充字节(这可能意味着您将获得不确定的值)。

但请注意,丢弃const限定符确实会导致定义不当的行为。

编辑

同样,强制转换(char**)ptr是无效的指针转换 - 仅此一项就是未定义的行为,因为它违反了严格的混叠。变量ptr本身被声明为char*,所以你不能对编译器撒谎并说"嘿,这实际上是一个char**",因为它不是。这与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("%sn", ptr);
}
}

给定

struct foo {int x, y;} s;
void write_int(int *p, int value) { *p = value; }

标准中没有任何内容可以区分:

write_int(&s.y, 12); //Just to get 6 characters

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 来正确处理使用指针执行任何"有趣"操作的代码,即使在标准明确定义的情况下也是如此,除非使用-fno-strict-aliasing调用。 在至少在标准的一些合理读数下定义的情况下,做出任何善意努力以避免任何不正确的别名"优化"的编译器在处理使用offsetof的代码时不会遇到任何问题,在这种情况下,所有将使用指针(或从它派生的其他指针)完成的访问都先于通过其他方式对对象的下一次访问。

最新更新