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});