对结构成员的不良访问

Unh*_*lig 3 c struct pointers undefined-behavior

由于内存的布局方式structs及其成员,我能够做到以下几点:

typedef struct
{
    int a;
    int b;
    int c;
} myStruct;
Run Code Online (Sandbox Code Playgroud)

main():

myStruct aStruct;
aStruct.a = 1;
aStruct.b = 2;
aStruct.c = 3;

int *aBadBoyPointer = &aStruct.a;

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));
Run Code Online (Sandbox Code Playgroud)

很容易.

以上行引发警告:

无序修改和访问 aBadBoyPointer

但它分别编译和运行精细打印1, 2, 3.

我的问题在这里:

为了正确地做到这一点,你能举出一个可能会破坏的场景吗,这个场景证实了编译器这是一个糟糕的做法/糟糕的做事方式?

或者:或许这实际上是在一些罕见情况下做事的"好方法"?

附录:

除了导致未定义行为的这部分:

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));
Run Code Online (Sandbox Code Playgroud)

我真正想知道的是:

它被认为是一个好的做法(一个好的做法),使用指向其中一个成员的指针,struct然后以struct这种方式获得对其他成员的访问(通过递增,由于struct成员的内存布局而成为可能)或者它是一个标准不赞成的做法?

其次如上所述,如果这是一个不好的做法,那么是否会出现在结构中以这种方式使用指针然后获得对某些情况下有益的另一个成员变量的访问的情况?

Sha*_*our 5

有一些问题,您看到的警告是由于您的函数参数的评估顺序未指定.在C99标准草案部分6.5.2.2 函数调用10说:

函数指示符的评估顺序,实际参数和实际参数中的子表达式是未指定的,但在实际调用之前有一个序列点.

你也在序列点内多次修改一个变量,这是一个未定义的行为,不能依赖它来工作,6.5 表达式2段说(强调我的前进):

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.72)此外,先前值应只读以确定要存储的值.73)

另外,请注意,标准允许在结构元素之间填充,但超出该标量被认为是一个元素的数组,因此增加超出数组然后执行间接也将是未定义的.这将在6.5.6 添加剂操作员7段中介绍:

出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为1 的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型.

并且超过一个超过一个数组边界访问一个数组边界是由部分6.5.6 添加运算符8段未定义:

[...]如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数.

我们可以看到根据优化级别gcc输出(见实时):

3 3 2
Run Code Online (Sandbox Code Playgroud)

或(现场直播):

3 3 3
Run Code Online (Sandbox Code Playgroud)

这两者都不是理想的输出.

通过指针访问结构成员的标准兼容方法是使用offsetofhere,这需要包括stddef.h.访问成员a看起来像这样:

*( (int*) ((char*)aBadBoyPointer+offsetof(myStruct, a)) )
    ^       ^                    ^
    3       2                    1
Run Code Online (Sandbox Code Playgroud)

这里有三个要素:

  1. 使用offsetof以确定偏移字节成员的
  2. 转换为*char**,因为我们需要以字节为单位的指针算法
  3. 回到*int**,因为这是正确的类型

  • @Andrey在调用函数之前有一个序列点,但对于每个参数.它不是逗号运算符而只是分隔符. (3认同)