void**是否属于严格别名规则的例外?开启严格别名时相关代码是否合法?
void**是不是严格别名规则的例外?带你理清背后的逻辑 嘿,咱们先把核心结论摆出来:从C标准的严格定义来看,用void**访问int*类型的对象确实属于严格别名违规,但你测试里看到的差异,并不是标准有例外,而是编译器为了兼容性做的特殊处理,或者说“网开一面”。
一、先搞懂严格别名规则到底是什么
严格别名规则是C标准给编译器留的优化空间,简单说就是:不同类型的指针不能随便用来读写同一块内存,除非是char*/unsigned char*/signed char*,或者是兼容类型、有共同初始序列的结构体这类明确例外。
void*本身是特殊的通用指针,标准允许任何对象指针转成void*再转回去,保证类型不变。但void**是指向void*的指针,它和int**(或者int*的指针)既不是兼容类型,也不在标准的例外列表里。
二、你的两段测试代码差异分析
1. char**版本的异常是预期的
你用char**的测试在-O2下出现错误输出,完全符合严格别名违规的表现:编译器认定int** a和char** b指向的是完全独立的内存,所以会直接优化掉if (*a)的判断——毕竟它刚给*a赋值了&v,按规则*b的修改不会影响*a,自然觉得*a不可能是NULL,最终导致错误输出。
2. void**版本的“正常”是编译器的妥协
换成void**后即使开-O2也没出问题,这不是标准的例外规定,而是两个现实原因:
- 首先,
void**在实际代码里太常用了(比如跨类型指针操作、回调参数传递等),编译器厂商为了兼容大量现有代码,特意对void**的别名操作放宽了优化限制,没有严格执行严格别名规则。 - 其次,在绝大多数系统上,
void*和int*的内存布局完全一致:都是指针类型,占相同字节数,存储的都是内存地址。所以通过void**修改int*的内存,不会出现字节层面的错误,编译器也可能因为这种布局一致性,没触发激进的优化。
但要记住:这只是编译器的“宽容”,不代表代码合法。换个更激进的编译器,或者未来GCC改了优化策略,这段代码依然可能出现未定义行为。
三、回到你最初的代码
void f(int *pi) { void **pv = (void **) π *pv = NULL; }
从标准角度看,这段代码确实违反了严格别名规则:你通过void**类型的指针pv,修改了int*类型对象pi的内存。虽然多数编译器能跑,但属于未定义行为,绝对不能依赖这种写法。
如果要合法实现类似逻辑,应该用标准允许的方式:
// 如果你想修改实参的指针(注意原代码改的是形参,这里调整为修改实参的写法) void f(int **pi) { // 合法方式:用void*中转,再转回来 void *tmp = NULL; *pi = (int*) tmp; } // 或者用标准允许的char*别名(不过没必要,这里只是示例) void f(int *pi) { char *pc = (char*)π memset(pc, 0, sizeof(pi)); }
总结
- C标准没有给
void**开严格别名的例外,用void**访问其他指针类型的对象属于未定义行为。 - 你看到的测试差异是编译器为了兼容性做的特殊处理,不是标准规定。
- 实际开发里一定要避开这种写法,遵循严格别名规则,用合法的方式操作指针。
内容的提问来源于stack exchange,提问作者rom1v

