如果目标对象是可简单复制的并且不是潜在重叠的子对象,那么什么决定可以使用 std::memset ?

dfr*_*fri 5 c++ memset language-lawyer

Cppreference 的页面指出std::memset

std::memset

// Defined in header <cstring>
void* memset( void* dest, int ch, std::size_t count );
Run Code Online (Sandbox Code Playgroud)

[...] 如果对象是潜在重叠的子对象或者不是 TriviallyCopyable(例如,标量、C 兼容结构或普通可复制类型的数组),则行为未定义。

标准中的哪些规则支持这一主张?

dfr*_*fri 2

(自我回答,因为我认为我在发布问题的过程中找到了完整的答案)


[cstring.syn]涵盖了我们需要转向 C 标准库头来string.h了解以下含义std::memset

namespace std {
  // ...
  void* memset(void* s, int c, size_t n);
  // ...
}
Run Code Online (Sandbox Code Playgroud)

/1 头文件的内容和含义与C标准库头文件<string.h>相同。[...]

例如,通过 N1570 的 C11 草案指定 C 标准库memset将一个字节 ( ) 复制到目标对象的unsigned char第一个字节中[重点是我的]:n

7.24.6.1 memset 函数

/1 概要

#include <string.h>
void *memset(void *s, int c, size_t n);
Run Code Online (Sandbox Code Playgroud)

/2 说明

memset 函数将 c 的值(转换为 unsigned char)复制到 s 指向的对象的前 n 个字符中的每一个中。

/3 返回

memset 函数返回 s 的值。

由于这是 C 标准“对象”,因此与 C++ 中的“对象”含义不同;第 3.15p1 节

执行环境中数据存储的对象区域,其内容可以表示值

考虑到这一点,我们回到 C++ 标准和[basic.types]/4,它告诉我们(C++) 对象的对象表示是底层对象的序列unsigned char,对于普通可复制类型,特别是,对象的值表示是对象表示的一部分

T 类型对象的对象表示是 T 类型对象占用的 N 个 unsigned char 对象的序列,其中 N 等于 sizeof(T)。[...]对于普通可复制类型,值表示是对象表示中确定值的一组位,该值是实现定义的一组值的一个离散元素42

脚注42阐明了此内存模型表示的意图,允许与 C 接口时的兼容性:

目的是 C++ 的内存模型与 ISO/IEC 9899 编程语言 C 的内存模型兼容。

[basic.types]/3扩展了从源对象到目标对象(两者都是普通可复制类型)的基础字节的按字节复制的效果,附加限制是这样的源对象和目标对象都不是字节复制操作应是潜在重叠的子对象

对于任何普通可复制类型 T,如果指向 T 的两个指针指向不同的 T 对象 obj1 和 obj2,其中 obj1 和 obj2 都不是潜在重叠的子对象,如果组成 obj1 的底层字节 ([intro.memory]) 被复制到obj2,41 obj2 随后应保持与 obj1 相同的值。

由此可知,该保留std::memset可用于设置对象的底层对象表示中的所有 ( n == sizeof(T)) 或对象的子集 ( n < sizeof(T)),该对象的类型是可简单复制的,并且该对象不是潜在重叠的子对象。unsigned char对象的结果值表示是特定对象表示的实现定义的值集的离散元素。但请注意,读取这些值不一定是明确定义的,一个臭名昭著的示例是std::memset在不遵守 IEEE754 的实现中将浮点的所有位设置为零,其中“所有位为零”可能表示陷阱。使用时要考虑的另一件事std::memset是,虽然它可能会创建正式定义良好的对象(在标准意义上),但它们的结果值可能会违反类不变量。