我无法在标准中找到该程序未定义的位置:
#include <iostream>
int main()
{
int *p;
{
int n = 45;
p = &n;
}
std::cout << *p;
}
§3.8对象生命周期中的所有情况似乎都不适用于此处。
答案 0 :(得分:6)
我不是100%肯定因为措辞,但看起来这是由3.8 / 6覆盖(我认为这种解释是正确的原因是因为3.8 / 5中的非规范性示例,{{1 }}):
...在对象的生命周期结束之后,在重用或释放对象占用的存储之前,可以使用引用原始对象的任何glvalue,但只能以有限的方式使用....程序具有未定义的行为如果:
然后第一个项目符号是罪魁祸首:// undefined behavior, lifetime of *pb has ended
:此转换必须在调用an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,
时发生,或者最后在读取整数值以进行格式化operator<<
时发生。 1}}代码。
答案 1 :(得分:1)
这当然是未定义的行为(根据常识和标准的措辞)。
就标准而言,3.8 / 5对于允许的内容和不允许的内容是相当具体的:
[...]在对象的生命周期结束后 之前对象占用的存储被重用或释放,任何指针指向对象所在的存储位置或者定位可能只能以有限的方式使用[...] 并使用指针,就像指针的类型为void *一样, 定义明确 允许间接[...],如下所述。如果:
的操作数
,程序具有未定义的行为 - ......
- [...]用作static_cast
的操作数,除非转换是指向cvvoid
的指针,或指向cvvoid
的指针,随后指向cv {{ 1}}或cvchar
- [...]用作unsigned char
对象的存储在每3.7.3 / 1范围的末尾结束(实际上这很可能不是真的,堆栈帧可能会在函数结束时重置,但正式那就是发生的事情)。因此,在生命周期之后但在之前释放存储时,不会发生取消引用。在发布存储后发生 因此,您可以取消引用指针的特殊条件不适用(对于具有相同前提条件的任何类似段落,例如3.8 / 6,情况也是如此)。
此外,假设前一段不是真的,只允许将指针解引用为cv dynamic_cast
或在解除引用之前将其强制转换为cv void*
(有符号或无符号)。换句话说,您不允许查看指向char
,就好像它是int
一样。如3.8 / 5中所述,int
在对象的生命周期之后实际上仅仅是int*
。这意味着将其解除引用为void*
相当于进行转换(不是明确地,但仍然)。
人们真的希望这种尝试产生错误,但我想这对于编译器来说是非常困难的。指针本身运行良好,并且通过获取有效对象的地址可以安全地获得它,这可能几乎不可能被诊断出来。
答案 2 :(得分:1)
*p
是一个glvalue。代码cout << *p
需要左值到右值转换。这由C ++ 14 [conv.lval]定义。
第2点列出了各种情况,并描述了每种情况下的行为。这些都不适用于*p
。特别是,最后一点是:
否则,glvalue指示的对象中包含的值是prvalue结果。
但是,*p
并不表示对象。
在[basic.life]部分中,有几个案例定义了左值到右值的转换,超出了[conv.lval]中的说法。这些情况与获取对象的存储时间有关,但我们不在对象的生命周期内。但是它们不适用于*p
,因为存储在前一个块结束时被释放。
因此,此代码的行为未定义
对某些东西感到不满意&#34;由于遗漏而未定义&#34;,我们总是希望看到一个具体的陈述&#34;这是未定义的行为&#34;确保我们没有忽视某些事情。但有时候就是这样。
答案 3 :(得分:-1)
首先根据 3.7.3自动存储持续时间对象的存储发布:
块范围变量显式声明了寄存器或非显式声明 声明静态或外部具有自动存储持续时间。存储 这些实体持续到创建它们的块 退出。
来自 3.8对象生命周期
在对象的生命周期开始之前但在存储之后 对象将占用哪个已被分配,或之后 对象的生命周期已经结束,在存储之前就已经存在了 对象占用被重用或释放,任何引用的指针 可以使用对象将位于或位于的存储位置 但只能以有限的方式
因此解除引用变量的指针,释放的存储空间导致UB