C11标准与主流编译器下结构体内部指针算术及比较合法性问询
首先给出问题前置定义:
我们定义如下结构体:
struct A { int x; int y; } a;
预设满足条件:offsetof(struct A, x) + sizeof(int) == offsetof(struct A, y),以下是四个问题的对应解答:
1. C11标准是否保证&a.x + 1 == &a.y的判定结果为真?
C11标准不提供该保证。
C标准中指针算术的有效范围仅限所属数组的边界,单个对象可以被视为长度为1的数组,&a.x + 1属于刚好超出数组边界的指针,标准仅保证该指针本身可以合法存在,不保证它和另一个独立成员对象a.y的地址相等,哪怕两者的偏移符合预设条件。
2. 若C11标准无相关保证,主流编译器是否会对该等价关系提供保障?
在无特殊编译配置的前提下,主流编译器(GCC、Clang、MSVC)在x86、ARM等常见架构下,针对该无填充字节的结构体定义,确实会满足&a.x + 1 == &a.y的等价关系,但这属于编译器实现层面的行为,不属于C标准强制要求的内容。如果开启了结构体强制对齐、随机填充等安全编译选项,该等价关系可能被打破。
3. 若上述等式成立,通过(&a.x)[1]访问a.y的值是否不会产生UB(未定义行为)?
即使地址完全相等,该访问行为依然属于未定义行为。
C标准的严格别名规则、指针算术有效性规则明确要求:指针只能用于访问其原本指向的对象(或所属数组内的元素)。&a.x的关联对象是a.x,哪怕&a.x + 1刚好等于a.y的地址,通过这个指针访问a.y也违反了标准约束,编译器有权基于该规则做优化,导致运行结果不符合预期。已有验证表明,至少在Clang编译器中,使用(&a.x)[1]访问a.y确实属于未定义行为。
4. 使用memcpy(&a.x+1, ...)的操作是否合法?
该操作的合法性分场景判断:
如果memcpy的读写长度不超过a.y的大小,且&a.x + 1确实等于a.y的地址,从实际内存访问的角度看操作不会触发内存错误,但从C标准的层面看依然存在合法性瑕疵:&a.x + 1本身没有关联到a.y对象,严格来说属于对无效指针的使用。
如果你的代码只针对特定编译器、特定架构部署,该操作在实践中可以正常运行,但不具备可移植性,也不符合C标准的规范要求。
内容的提问来源于stack exchange,提问作者tstanisl

