在阅读this时,我看到了一个我不理解的UB,希望你能澄清一下
size_t f(int x)
{
size_t a;
if(x) // either x nonzero or UB
a = 42;
return a;
}
我猜UB是由于a
没有初始值,但不是它定义的行为?意思是,f(0)
将返回变量a
所持有的值,无论它是什么(我认为这类似于rand()
)。我们必须知道代码片段返回的代码具有定义明确的行为的价值吗?
答案 0 :(得分:5)
含义,f(0)将返回变量a持有的值,无论它是什么......
嗯,在你的情况下,
a
是自动本地变量所以,是的,根据定义,这会导致undefined behavior。
引用C11
,章节§6.3.2.1
[...]如果 左值指定了一个自动存储持续时间的对象 声明与寄存器存储类(从未有过其地址)和该对象 未初始化(未使用初始化程序声明,并且没有对其进行任何分配) 在使用之前执行),行为未定义。
答案 1 :(得分:2)
补充到@ SouravGhosh的回答,重要的是理解具有未定义的行为是语言构造的某些组合和某些运行时评估的程序可以执行的一个属性是重要的,<强> 如通过标准指定EM> 即可。它不是分析编译器或程序可能做什么的功能;事实上,它恰恰相反:编译器和程序的许可证,从任何特定约束条件中释放它们。
因此,尽管该标准在声明UB方面是相当合乎逻辑且一致的,但从询问为什么特定构造具有UB或为什么特定评估可能或确实展示UB的方向来处理问题并没有太大用处。 标准的原因指明了它的作用,但是为什么一个东西有UB的主要答案总是“因为标准这样说。”
答案 2 :(得分:2)
未定义行为是一种实现许可,用于以作者认为最适合预期目的的任何方式处理代码。一些实现包括在没有先写入的情况下读取自动变量的情况下陷阱的逻辑,即使这些类型没有陷阱表示;标准的作者几乎肯定知道这种行为并认为它有用。标准仅指定事物可能陷阱的一种情况,但仅以定义的方式(从较大的整数类型转换为较小的整数类型);在事情可能陷入困境的所有其他情况下,标准的作者只是将行为保留为未定义,而不是试图详细说明特定陷阱的工作方式,是否可以恢复等等。
此外,自动变量通常映射到比所讨论的变量大的寄存器,甚至没有陷阱表示的类型在这种情况下也可能表现得很奇怪。例如,考虑一下:
volatile uint16_t v;
uint32_t x(uint32_t a, uint32_t b)
{
uint16_t temp;
if (b) temp=v;
return temp;
}
如果b非零,则temp
将加载v
,加载v
的行为将导致temp
保留一些值0-65535 。但是,如果b
为零,则编译器不能使用temp
加载v
(因为volatile限定符)。如果temp
已分配给32位寄存器(在某些平台上,它可能在逻辑上被分配了与a
相同的那个),则该函数可能表现为temp
持有大于65535的值。标准允许这种可能性的最简单方法是在上述情况下返回temp
将是未定义行为。并不是因为在调用者最终忽略返回值的情况下(如果调用者将使用返回值,调用者可能不会通过b == 0),它会期望实现会做任何特别不稳定的事情。因为把事情留给实施者的判断比试图为这些事情制定完美的“一刀切”规则更容易。
现代C实施者不再将未定义行为视为执行判断的邀请,而是作为假设不需要判断的邀请。因此,即使未初始化值的值被用于任何目的,除非通过不知道它是否有意义的代码传递给最终忽略它的代码,它们的行为可能会破坏程序执行。