通过定义好的偏移来访问成员吗?

Ben*_*fan 6 c offsetof undefined-behavior language-lawyer char-pointer

在使用指针算法时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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这应该打印"那里","你呢?" 并且"很好".连续三行,它目前与clang和gcc一起使用,因为你可以在wandbox上验证自己.但是,我不确定这些指针强制转换和算术是否违反某些规则会导致行为变为未定义.

Lun*_*din 1

据我所知,这是明确定义的行为。但这仅仅是因为您通过类型访问数据char。如果您使用其他指针类型来访问该结构,则这将是“严格别名违规”。

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

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

编辑

类似地,强制转换(char**)ptr是无效的指针转换 - 这本身就是未定义的行为,因为它违反了严格的别名。变量ptr本身被声明为 a 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("%s\n", ptr);
    }
}
Run Code Online (Sandbox Code Playgroud)