【问题标题】:Consequences of uninitialised variables: int vs unsigned char未初始化变量的后果:int vs unsigned char
【发布时间】:2018-01-12 08:50:48
【问题描述】:

我在cppreference.com看到了下面的例子

int x;     // OK: the value of x is indeterminate
int y = x; // undefined behavior

这里,int y = x;未定义的行为,因为 x 未初始化。

但是,

unsigned char c;     // OK: the value of c is indeterminate
unsigned char d = c; // OK: the value of d is indeterminate

这里,unsigned char d = c;不确定的行为,但unsigned char c; 也是一个未初始化的变量。

那么,为什么unsigned char d的值是不确定的?

【问题讨论】:

  • 在示例上方的框中对此进行了说明。是否有特定的部分没有意义?
  • @SouravGhosh 如果我遗漏了什么,请原谅我,获取 x 的地址有何改变?
  • @rsp 你是在问为什么 unsigned char 是不同的,还是你只是要求一个简单的区别说明,例如 JETM 给出的?目前这实际上是一个非常不清楚的问题。
  • @SouravGhosh 是的,一样。它也可以是UB。即使 x 已获取地址,它也可以在寄存器中直到&x,在这种情况下,寄存器可以处于“未初始化”状态,这将是一个陷阱表示。
  • @Aconcagua 可能是因为unsigned char 保证没有陷阱表示。如果体系结构存在寄存器问题,则由编译器使用“Something”(非陷阱)初始化寄存器(如果它用于 char)。

标签: c++ initialization language-lawyer undefined-behavior


【解决方案1】:

cppreference.com 这样的在线参考在一定程度上是不错的。但众所周知,有时错误或误解确实偶尔会漏掉。所以在处理这些奇怪的事情时,去官方的 C++ 标准总是一件好事。

N3936

§8.5 初始化程序 [dcl.init]

12 [...] 使用自动或动态存储来存储对象时 获得持续时间,对象具有不确定的值,如果 没有对对象执行初始化,该对象保留一个 不确定值,直到该值被替换(5.17)。 [...] 如果 评估产生不确定的值,行为是 未定义,以下情况除外:

  • 如果通过评估产生无符号窄字符类型 (3.9.1) 的不确定值

    • [...]

    • 强制转换或转换为无符号窄字符类型(4.7、5.2.3、5.2.9、5.4)的操作数

    • [...]

    那么运算的结果就是一个不确定的值。

  • 如果通过评估简单赋值的右操作数产生不确定的无符号窄字符类型值 运算符(5.17),其第一个操作数是无符号窄的左值 字符类型,一个不确定的值替换 左操作数引用的对象

  • 如果在初始化表达式的计算过程中产生不确定的无符号窄字符类型值 初始化无符号窄字符类型的对象,该对象 被初始化为一个不确定的值。

例子:

int f(bool b) {
  unsigned char c;
  unsigned char d = c; // OK, d has an indeterminate value
  int e = d; // undefined behavior
  return b ? d : 0; // undefined behavior if b is true
}

所以(令我惊讶的是)标准支持这一点。

至于为什么,最可能的原因也可以在标准中找到:

§3.9.1 基本类型 [basic.fundamental]

1 [...] 对于无符号窄字符类型,所有可能的位模式 的值表示表示数字。 这些要求确实 不适用于其他类型


作为旁注,我刚刚意识到这可以被邪恶的面试官使用:

问。您能否以明确定义的行为将对象的有效值更改为未确定的值?如果是,怎么做?

一个。

unsigned char ind;
unsigned char x = 24;
x = ind; // x had a valid value, now x has an indetermined value

【讨论】:

  • @M.M 可能是因为当 d 具有不确定值时评估 d 是 U.B.因为它可能是一个位模式,它不是它的类型的有效值表示。也许他们只是想让它保持简单(r)。我们只能推测,除非出现官方理由或至少标准委员会成员的讨论。
  • @M.M 例如一个虚构的平台。这个平台有一个奇怪的int 编码,其中int 的位模式无效。从intchar 的转换是通过虚构指令movic 完成的。如果源(int)具有无效值,则在此平台上movic 发出硬件异常。这个平台理论上可以存在,并且理论上会得到标准的支持。因此需要UB。我知道这有点牵强,但 C++ 因想要支持虚构的深奥硬件而臭名昭著。
  • @bolov 那个虚构平台的名字是安腾。 Raymond Chen 有一篇关于此的博文"Uninitialized garbage on ia64 can be deadly"
  • 对于你这个邪恶的面试问题,我可以通过placement new来解决。
  • 可能要注意,您引用的条款仅适用于 C++14 及更高版本。在此之前,访问未初始化的 unsigned char 的值是正式未定义的。
【解决方案2】:

从您引用的页面:从不确定的值分配是未定义的行为除了

如果将无符号窄字符类型或std::byte的不确定值分配给另一个无符号窄字符类型或std::byte的变量(变量的值变为不确定,但行为未定义)

我相信这是因为默认初始化可能会将任何位组合放入变量中,而标准保证无符号窄字符类型可以采用每个可能的位模式表示的值,但对于其他类型则没有这样的保证。

【讨论】:

  • 特别是这个参考; eel.is/c++draft/basic.fundamental#1 对于无符号窄字符类型,值表示的每个可能的位模式代表一个不同的数字。这些要求不适用于其他类型。
  • @Niall:保证无符号字符类型没有填充位或陷阱表示,并且是唯一具有这些保证的类型。这使它们成为唯一可以始终提供某些其他保证的类型。不幸的是,标准没有说明其他类型没有填充位或陷阱表示的质量实现是否应该或不应该这样做。
【解决方案3】:

来自the linked page

使用通过默认初始化任何类型的非类变量获得的不确定值是未定义行为[...],但以下情况除外:

...

如果无符号窄字符类型或std::byte的不确定值用于初始化另一个无符号窄字符类型或std::byte的变量;

unsigned char 是无符号窄字符,因此这是不出现 UB 的例外情况之一。

【讨论】:

【解决方案4】:

C++ 中包含的两个相关的 C 有用特性是:

  1. 可以通过复制其中包含的所有单个字节来复制对象。

  2. 即使其中的某些对象不包含定义的值,也可以安全地完整复制结构类型对象,前提是不尝试在整体上下文之外读取未定义部分或其副本-结构复制或单个字节访问。

在大多数平台上,同样的保证不能也不应该扩展到其他类型并没有什么特别的原因,但是 C 标准的作者只是试图定义应该适用于所有平台的保证,并且C++ 标准的作者只是简单地遵循了 C++ 的行为。

【讨论】:

  • 问题标记为 c++
  • @bolov:我描述的特性早在 C++ 被认为不太标准化之前就已经存在于 C 中了。除了首先在 C 中以这种方式定义之外,我认为没有理由相信该行为是在 C++ 中定义的。这反过来意味着它在 C 中定义的原因与在 C++ 中定义的原因相同。
猜你喜欢
  • 1970-01-01
  • 2020-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
  • 2021-04-16
  • 1970-01-01
相关资源
最近更新 更多