是否使用未初始化的无符号类型对象未定义行为?

Is using an uninitialized unsigned type object Undefined Behavior?

我知道使用(访问值)类型为内置整数类型的未初始化的非静态和非全局对象是未定义的行为。

int x; // x is defined inside a function scope for example main
++x;// UB

signed char c = 127; // max positive value for char on my machine

c++; // UB overflowing a signed char.

这里没问题,因为无符号数溢出会丢弃超出范围的位。

来自 cppreference :

Use of an indeterminate value obtained by default-initializing a non-class variable of any type is undefined behavior

具有不确定值的对象不同于您不知道其值的对象。使用不确定的值是未定义的行为(除了少数例外,请参见上面的 link),即使对象可能具有的每个可能值都会定义行为。

假设具有不确定值的对象具有其任何一个可能值是错误的。 ++x 是未定义行为的原因不是因为它可能已经具有最大值和溢出。这是未定义的行为只是因为 x 没有初始化。

Is using an uninitialized unsigned type object Undefined Behavior?

是的。

but I think it doesn't cause any harm.

你可能是对的。但因为它是 UB,你也可能错了。 你永远无法确定

编译器做的一件偷偷摸摸的事情是检测您的未定义行为并通过彻底删除到达此 UB 的代码路径来“优化”您的程序。

Example(程序编译,但什么都不做)

如果未初始化的对象需要表现得好像它们是用一些未指定的位模式初始化的,这将阻碍一些有用的优化和诊断。虽然有时程序能够对未初始化的对象执行某些操作可能很有用(例如,如果代码初始化组中对象的变量子集,并希望复制所有已初始化的对象,盲目地复制所有对象这些对象可能比尝试 select 仅复制初始化的对象或初始化其值影响程序执行的对象更快),在其他时候诊断或优化会更有帮助。因为标准的作者不可能在每种情况下都知道让实现将未初始化的值当作是用未指定的位模式初始化的、在读取时导致陷阱或以其他可能有助于优化,它们允许实现以对客户最有用的任何方式处理此类情况。

考虑,例如:

struct thing { uint32_t count; uint16_t dat[126]; };
struct thing x,y,z;

void test(void)
{
  struct thing temp;
  temp.count = readInteger();
  for (int i=0; i<temp.count; i++)
    temp.dat[i] = readInteger;
  x = temp;
  y = temp;
  z = y;
}

根据代码的预期目的,至少有五种方法可能有助于实现处理它:

  1. temp 视为未写入的部分用任意位模式初始化,然后将其复制到 xyz(这意味着两个结构都将保证包含匹配的未指定数据)。

  2. 在未完全初始化其内容的情况下尝试复制 temp,因为如果执行上下文可以访问机密数据,复制未初始化数据的代码可能会带来安全风险。

  3. 将自动对象的未初始化部分的每次读取都视为可能产生独立的未指定值,从而允许编译器保留 x 和 [=13 的相应部分=] 保存任何方便的值(包括它们在函数调用之前碰巧保存的任何值),但将不确定值的存储处理为静态持续时间或堆持续时间对象,就好像它写入了一个未指定的位模式(因此未写入的部分yz 将保证相互匹配,即使它们可能不匹配 x).

  4. 将每次读取不确定值都视为产生“传染性”不确定值,从而允许编译器保留 xyz 持有任何方便的值,但不保证它们匹配。

  5. 如果 temp.count 不等于 126,代码将调用 UB 这一事实基于它“将”始终等于 126 的事实来优化调用代码。

由于上述行为中的 none 在每种情况下都是最有用的,因此标准允许编译器 select 以对其客户最有用的任何方式进行处理。