N2346标准示例中将goto替换为迭代语句产生UB的原因是什么
问题核心:你混淆了自动存储对象的「生命周期边界」和「初始化求值时机」两个独立规则,标准条款之间没有矛盾,你的推导漏看了自动对象生命周期的终止规则。
1. 原始goto示例的合法逻辑
首先看6.5.2.5/15的goto示例,复合字面量(struct s){ j++ }的封闭块是整个f()的函数体块,根据N2346::6.5.2.5/5的规定,它对应的自动对象生命周期覆盖整个函数执行期:
- 每次goto跳回
again标签时,确实会按照N2346::6.8/3的规则,重新对复合字面量的初始化器求值、把新值写入同一个全局的无名对象,从头到尾只有这一个对象存在,所以p和q最终指向同一个地址,q->i == 1的访问是合法的,函数返回1符合标准要求。
2. 替换为迭代语句后触发UB的原因
如果把goto替换为迭代语句(比如for/while循环),代码结构会变成下面这样:
struct s { int i; }; int f (void) { struct s *p = 0, *q; int j = 0; while (j < 2) { // 循环体是独立的内层块 q = p, p = &((struct s){ j++ }); } return p == q && q->i == 1; }
此时复合字面量的封闭块变成了循环体的内层块,根据C标准对自动存储周期的基础规则N2346::6.2.4/6:进入块时为自动对象分配存储,退出块时释放存储,对象生命周期终止。
这时候执行流程会出现问题:
- 第一轮循环进入块,创建复合字面量对应的无名对象,p指向该对象
- 第一轮循环结束,退出循环体块,该无名对象的生命周期直接终止,p变成野指针(持有无效地址)
- 第二轮循环进入块,你首先把野指针p赋值给q,哪怕第二轮新创建的复合字面量对象刚好和之前的地址相同,你访问
q->i也是对已经失效的对象的解引用,直接触发未定义行为。
3. 你的推导错误点
你提到的N2346::6.8/3规则完全正确,迭代场景下每轮循环确实会重新对初始化器求值、创建新的无名对象,但这个规则只保证当前轮次的对象是合法的,不影响上一轮对象生命周期结束后指针失效的事实:你持有了已经销毁的对象的指针,再解引用这个指针的行为和新对象是否创建没有任何关系,自然触发UB。
内容的提问来源于stack exchange,提问作者Some Name
相关产品推荐
相关产品推荐

