微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么 unsigned char 具有与其他数据类型不同的默认初始化行为?

如何解决为什么 unsigned char 具有与其他数据类型不同的默认初始化行为?

我正在阅读有关认初始化的 cppreference 页面,我注意到一个部分说明了以下内容

//UB
int x;
int y = x;        
   
//Defined and ok
unsigned char c;
unsigned char d = c;

对于无符号字符,同样的规则也适用于 std::byte。

我的问题是,如果您在分配之前尝试使用该值(如上例),而不是 unsigned char,那么为什么所有其他非类变量(int、bool、char 等)都会导致 UB?为什么 unsigned char 很特殊?

The page I am reading for reference

解决方法

区别不在于初始化行为。 uninitialised int 的值是不确定的,默认初始化使它不确定。未初始化的 unsigned char 的值是不确定的,默认初始化使它不确定。没有区别。

不同之处在于产生 int 类型的不确定值的行为 - 或除异常 unsigned char 或 std::byte 之外的任何其他类型 - 是未定义的(除非该值被丢弃)。

当不确定值被正确定义时,unsigned char(以及后来的 std::byte)的异常被添加到 C++14 的语言中(尽管由于更改是一个缺陷解决方案,据我所知适用于当时的官方标准,C++11)。

我找不到该设计选择的文件依据。以下是定义的时间表(所有标准引述均来自草稿):

C89 - 1.6 术语定义

未定义行为 --- 行为,在使用...不确定值对象时


C89 - 3.5.7 初始化 - 语义

...如果一个具有自动存储期的对象没有明确初始化,它的值是不确定的。

任何类型都没有例外。在阅读 C++98 标准时,您会明白为什么 C 标准是相关的。

C++98 - [dcl.init]

...否则,如果没有为对象指定初始化器,则该对象及其子对象(如果有)具有不确定的初始值

没有定义不确定值的含义或使用它时会发生什么。预期含义可能大概与 C89 相同,但未详细说明。

C99 - 3. 术语、定义和符号 - 3.17.2

3.17.2 不确定值

未指定的值或陷阱表示

3.17.3 未指定值

本国际标准对在任何情况下选择哪个值没有要求的相关类型的有效值

注意未指定的值不能是陷阱表示。


C99 - 6.2.6 类型表示 - 6.2.6.1 总则

某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且被没有字符类型的左值表达式读取,则行为未定义。如果这种表示是由没有字符类型的左值表达式修改对象的全部或任何部分的副作用产生的,则行为是未定义的。 41) 这种表示被称为陷阱表示。


C99 - J.2 未定义行为

在以下情况下行为未定义:

  • ...
  • 具有自动存储期限的对象的值在不确定时使用
  • 陷阱表示由没有字符类型的左值表达式读取
  • 陷阱表示是由使用不具有字符类型的左值表达式修改对象的任何部分的副作用产生的
  • ...

C99 引入了术语陷阱表示,并且在使用时也有 UB,就像不确定值一样。字符类型(char、unsigned char 和 signed char)没有陷阱表示,可用于操作其他类型的没有 UB 的陷阱表示。

C++ 核心语言问题 - 616.“不确定值”的定义

C++ 标准使用短语“不确定值”而没有定义它。 C99 将其定义为“未指定的值或陷阱表示”。 C++ 是否应该效仿?

提议的决议(2012 年 10 月):

[dcl.init]第12段如下:

如果没有为对象指定初始化程序,则该对象是默认初始化的。当获取一个自动或动态存储期限的对象的存储时,该对象具有一个不确定值,如果没有对该对象进行初始化,该对象将保留一个不确定值,直到该值被替换(5.17 [expr.ass]) . [注意:具有静态或线程存储持续时间的对象是零初始化的,参见 3.6.2 [basic.start.init]。 —end note] 如果评估产生不确定值,则行为未定义,但以下情况除外:

  • 如果无符号窄字符类型 (3.9.1 [basic.fundamental]) 的不确定值是通过以下评估产生的:
  • 条件表达式 (5.16 [expr.cond]) 的第二个或第三个操作数,
  • 逗号的右操作数 (5.18 [expr.comma]),
  • 强制转换或转换为无符号窄字符类型的操作数(4.7 [conv.integral]、5.2.3 [expr.type.conv]、5.2.9 [expr.static.cast]、5.4 [expr.演员]),或
  • 丢弃值表达式(第 5 条 [expr]),

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

如果对第一个操作数是左值的简单赋值运算符 (5.17 [expr.ass]) 的右操作数的求值产生了无符号窄字符类型 (3.9.1 [basic.fundamental]) 的不确定值无符号窄字符类型,一个不确定的值替换左操作数引用的对象的值。

如果在初始化无符号窄字符类型的对象时通过对初始化表达式的求值产生了无符号窄字符类型 (3.9.1 [basic.fundamental]) 的不确定值,则该对象将被初始化为不确定值。

提议的更改被接受为具有一些进一步更改的缺陷解决方案(问题 1213),但基本保持不变(对于此问题的目的足够相似)。这就是 unsigned char 的例外似乎已被引入 C++ 的地方。据我所知,核心语言问题没有公开评论或说明异常的理由。

,

在 C89 和 C99 下,未初始化的值可以具有任何位模式。如果可寻址位置有 n 位,那么 unsigned char 保证有 2ⁿ 个可能的值,因此每个可能的位模式都是有效值。然而,其他类型在某些平台上将以并非所有位模式都有效的方式存储。当存储的位模式不代表有效值时,如果代码试图读取一个对象,该标准没有对可能发生的情况强加任何要求,因此读取除 unsigned char 以外的类型的对象是否会产生一个问题未指定值或可能触发任意行为,将取决于实现的指定类型表示是否为所有可能的位模式分配了有效值。

C11 标准增加了一个附加条件,即即使是指定所有对象(无论其地址是否被获取)的实现都将始终以所有位模式表示有效值的方式存储,也可以选择以完全任意的方式运行如果试图访问一个未初始化的对象,该对象不是一个 unsigned char 其地址被采用。尽管没有针对 C11 发布基本原理文档(与早期版本不同),但我认为此类更改源于对于标准是否应该仅描述 100% 可移植程序的行为,还是更广泛的实用程序的行为缺乏共识。如果程序将在完全未指定的实现上运行,那么除了 C11 标准指定的情况外,将不可能知道读取未初始化对象的效果。如果它将在已知的实现上运行,那么它将被处理,但是实现决定处理它,无论标准是否强制要求行为,因此不需要特别强制任何东西。不幸的是,一个 Gratuitously “Clever” Compiler 的作者认为,当标准将一个动作描述为“不可移植或错误”时,它的真正含义是“不可移植,因此是错误的”,尽管这样的概念直接矛盾为早期版本的标准发布的基本原理文档。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。