6

使用 进行指针运算时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);
    }
}

这应该打印“那里。”,“你?” 和“好”。在三个连续的行上,它目前使用 clang 和 gcc,因为您可以在wandbox上验证自己。但是,我不确定这些指针转换和算术是否违反了某些会导致行为未定义的规则。

4

2 回答 2

1

给定

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 来正确处理使用指针做任何“有趣”的代码,即使在 Standard 明确定义的情况下,除非使用-fno-strict-aliasing. 在至少在标准的一些合理解读下定义的情况下,做出任何善意努力以避免任何不正确的别名“优化”的编译器在处理offsetof将使用指针完成所有访问的情况下使用的代码将毫无问题(或从它派生的其他指针)在通过其他方式对对象的下一次访问之前。

于 2017-10-03T18:44:20.857 回答
1

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

严格来说,越界访问数组不是很好定义的,但是使用字符类型指针从结构中抓取任何字节是很好定义的。通过使用offsetof你保证这个字节不是一个填充字节(这可能意味着你会得到一个不确定的值)。

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

编辑

同样,(char**)ptr强制转换是无效的指针转换——这本身就是未定义的行为,因为它违反了严格的别名。变量ptr本身被声明为 a char*,所以你不能对编译器撒谎说“嘿,这实际上是 a 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);
    }
}
于 2017-10-02T11:51:09.770 回答