C++ 读取和比较POD类型的填充字节是否是未定义的行为?
今天,我遇到了一些代码,大致类似于下面的代码片段。C++ 读取和比较POD类型的填充字节是否是未定义的行为?,c++,language-lawyer,padding,undefined-behavior,standard-layout,C++,Language Lawyer,Padding,Undefined Behavior,Standard Layout,今天,我遇到了一些代码,大致类似于下面的代码片段。valgrind和undefinedbehaviorsanizizer都检测到未初始化数据的读取 template <typename T> void foo(const T& x) { static_assert(std::is_pod_v<T> && sizeof(T) > 1); auto p = reinterpret_cast<const char*>(&
valgrind
和undefinedbehaviorsanizizer
都检测到未初始化数据的读取
template <typename T>
void foo(const T& x)
{
static_assert(std::is_pod_v<T> && sizeof(T) > 1);
auto p = reinterpret_cast<const char*>(&x);
std::size_t i = 1;
for(; i < sizeof(T); ++i)
{
if(p[i] != p[0]) { break; }
}
// ...
}
从POD类型读取填充字节并将其与其他内容进行比较是否是未定义的行为?无论是在标准中还是在StackOverflow上,我都找不到明确的答案。程序的行为是基于两个方面定义的:
1) 在C++14之前:由于您的
char
类型可能有1的补码或有符号大小signed
,因此比较+0和-0可能会返回一个令人惊讶的结果
真正防水的方法是使用const unsigned char*
指针。这消除了对现在已废除的(从C++14)1的补码或带符号的大小char
的任何担忧
由于(i)您拥有内存,(ii)您使用指向
x
的指针,(iii)无符号字符
不能包含陷阱表示,(iv)无符号字符,无符号字符
,以及有符号字符
不受严格的别名规则约束,使用const unsigned char*
读取未初始化内存的行为定义得很好
2) 但由于您不知道未初始化内存中包含的内容,因此读取内存时的行为是未指定的,这意味着程序行为是由实现定义的,因为字符类型不能包含陷阱表示。这取决于条件 如果
x
初始化为零,则填充具有零位,因此这种情况定义良好(C++14的8.5/6):
将类型为T的对象或引用初始化为零意味着:
-如果T是标量类型(3.9),则将对象初始化为值
通过转换整数文本获得
0(零)到T;105
-如果T是(可能是cv合格的)非联合类类型,则每个
非静态数据成员和每个基类
子对象初始化为零,填充初始化为零位
-如果T是(可能是cv限定的)联合类型,则对象的第一个
非静态命名数据成员为零-
初始化,填充被初始化为零位
-如果T是数组类型,则每个元素初始化为零;-如果T是一个
引用类型,不执行初始化
但是,如果默认初始化了x
,则未指定填充,因此它具有不确定的值(根据此处未提及填充的事实推断)(8.5/7):
默认初始化T类型的对象意味着:
-如果T是(可能是cv合格的)类别类型(第9条),则默认
调用T的构造函数(12.1)(初始化为
如果T没有默认构造函数或重载解析,则格式错误
(13.3)导致歧义或功能被删除或
无法从初始化上下文访问)
-如果T是数组类型,则每个元素默认初始化
-否则,不执行初始化
在这种情况下,比较不确定值是UB,因为当您将不确定值与某物(8.5/12)进行比较时,上述例外情况均不适用:
如果没有为对象指定初始值设定项,则该对象为
默认值已初始化。当存储对象时,使用自动或
获取动态存储持续时间时,对象具有不确定的
值,并且如果没有对对象执行初始化,则
对象保留一个不确定的值,直到该值被替换为止
(5.17). [注意:具有静态或线程存储持续时间的对象是
零已初始化,如果存在不确定值,请参见3.6.2.-结束注释]
通过评估产生,行为未定义,除非
以下情况:
-如果为无符号窄字符类型的不确定值(3.9.1)
通过对以下各项的评估得出:
……条件表达式(5.16)的第二个或第三个操作数
……逗号表达式的右操作数(5.18)
……转换或转换为无符号窄字符类型(4.7、5.2.3、5.2.9、5.4)的操作数
或
……一个废弃的值表达式(第5条),然后是
操作是一个不确定的值
-如果无符号窄字符类型的不确定值为
由简单赋值的右操作数求值产生
运算符(5.17),其第一个操作数是无符号字符串的左值
字符类型,不确定的值将替换
由左操作数引用的对象
-如果不确定的值为
无符号窄字符类型由
初始化未签名的对象时的初始化表达式
窄字符类型,该对象初始化为不确定字符
价值观
< PaseBBA的回答正确地描述了C++标准的字母。 坏消息是,我测试过的所有现代编译器(GCC、Clang、MSVC和ICC)都忽略了这一点上的标准字母。相反,他们用C标准来处理秃顶的语句 [如果]在不确定时使用具有自动存储持续时间的对象的值,则行为未定义
似乎是100%的规范,C和C++都是,即使附件J不规范。这适用于对未初始化存储的所有可能读访问,包括通过
unsigned char*
仔细执行的读访问,是的,包括对填充字节的读访问
此外,如果你要提交错误报告,我会
struct obj { char c; int* i; };
foo(obj{'b', nullptr});