You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

非相同结构体类型指针转换后访问成员的行为合法性及安全性问询

非相同结构体类型指针转换后访问成员的行为合法性及安全性问询

嘿,这个问题问得挺钻细节的,咱先把核心结论摆出来:这段代码属于C标准定义的未定义行为——哪怕它现在在你的编译器上能正常打印结果,这也只是巧合,不是标准保证的,绝对不能依赖这种写法。

下面给你拆解为啥那行“有意思的代码”有问题:

1. 指针算术的合法性问题

你写的struct H *h = (struct H *) p - 1;这行,直接违反了C标准里对指针算术的硬性要求:

  • C标准规定,指针算术只能用于指向同一个数组对象的元素,或者数组末尾的下一个位置(单个对象会被视为长度为1的数组)。
  • 这里p本来是指向struct Gdata成员的指针,你先把它强转成struct H*,但这个指针根本不指向任何struct H对象或者struct H数组的元素,对它做减法操作完全是标准不认可的行为——哪怕你手动算出来的地址刚好是g.onref的地址,编译器也没有义务保证这个计算逻辑是正确的。

2. 严格别名规则的违反

就算地址计算碰巧蒙对了,你用struct H*去访问struct G的成员,也触发了严格别名规则的违规:

  • C标准明确要求,你只能用对象的实际类型、它的有符号/无符号等价类型、char/unsigned char类型,或者少数兼容类型来访问对象。
  • 这里g.onrefstruct G的成员,你却用struct H*onref成员去读写它,这属于用无关类型的指针访问对象,编译器可能会因为严格别名优化,把这个访问操作直接优化掉,或者生成逻辑错误的机器码——比如开启O2优化后,可能直接输出随机值,甚至程序崩溃。

为啥现在能正常运行?

说白了就是你当前的编译环境刚好“纵容”了这个违规操作:

  • 你的编译器里,struct Honref是最后一个成员,struct Gonref是第一个成员,内存偏移碰巧凑对了;
  • 同时你没开启严格别名相关的优化(比如GCC的-fstrict-aliasing,默认在O2及以上等级开启),编译器没去做激进的代码优化。

正确的实现方式

如果你想实现这种“用标记位区分不同类型对象”的功能,得用符合标准的写法,比如用公共基结构体:

#include <stdio.h>
#include <inttypes.h>
#include <stddef.h>

// 定义公共基结构体,统一存放类型标记
struct BaseTag {
    uint64_t onref;
};

struct H {
    struct BaseTag tag;
    struct H *other;
    uint32_t *map;
};

struct G {
    struct BaseTag tag;
    char data[10];
};

#define ONREF_NOT_H 42

void foo(void *p) {
    // 用offsetof宏计算data在G中的偏移,反向定位到公共基结构体的地址
    struct BaseTag *tag_ptr = (struct BaseTag*)((char*)p - offsetof(struct G, data));
    printf("onref: %"PRIu64"\n", tag_ptr->onref);
}

int main() {
    static struct G g;
    g.tag.onref = ONREF_NOT_H;
    foo(&g.data);
}

这种写法的优势很明显:

  • 用标准定义的offsetof宏计算地址,完全符合C标准要求;
  • 用公共基结构体的指针访问标记位,不会触发严格别名问题;
  • 不管编译器怎么优化、怎么调整结构体内存布局,都能稳定工作。

最后再啰嗦一句:未定义行为的代码就像走钢丝,现在没摔不代表永远不会摔——赶紧改成标准合规的写法,别给自己留坑!

火山引擎 最新活动