std::memcpy或显式字符值赋值-在C++中等于合法

std::memcpy or explicit char value assignment - equal and legal in C++

本文关键字:C++ 赋值 memcpy 字符 std      更新时间:2023-10-16

在以下示例中,uint32_t的值表示被复制到uint8_t数组。这是由std::memcpy完成的。根据我对C++标准的理解,这是完全合法的:我们正在通过向unsigned char*广播的T*访问T类型的对象。没有混叠问题,没有对齐问题。

反之则不那么明显。我们正在通过unsigned char*访问T的对象表示,这是合法的。但是,访问一词是否包括更改

当然,没有混叠和对齐问题。然而,如果缓冲区s中的值来自外部源,则会出现问题:我们必须确保正确的端序并省略陷阱表示。可以检查右端序,这样就可以解决这个问题。但是陷阱表示呢?我们如何才能避免这种情况?或者uint类型没有陷阱表示,而不是说double

我知道另一种(更符合要求的?)方式是将uint8_t值转换为uint_t对象。我们仍然必须遵守endianness,但这应该可以安全地省略陷阱表示。

但是,在小µC(8位)上进行大类型的移位可能相当昂贵!

下一个问题是,第二次尝试(见下文代码)在合法性和功能性方面是否等同于memcpy方法?看起来memcpy版本对优化器更友好。

#include <cstdint>
#include <cstring>
#include <cassert>
typedef uint32_t utype;
constexpr utype value = 0x01020304;
int main() {
utype a{value};
utype b{0};
uint8_t s[sizeof(utype)]{};
// first     
std::memcpy(s, &a, sizeof(utype));
assert(s[0] == (value & 0xff));
std::memcpy(&b, s, sizeof(utype));
assert(b == value);
// second    
const uint8_t* ap = reinterpret_cast<const uint8_t*>(&a);
s[0] = ap[0]; // explicitly legal in C++
s[1] = ap[1];
s[2] = ap[2];
s[3] = ap[3];
assert(s[0] == (value & 0xff));
uint8_t* bp = reinterpret_cast<uint8_t*>(&b);
bp[0] = s[0]; // same as memcpy or ist this UB ?
bp[1] = s[1];
bp[2] = s[2];
bp[3] = s[3];
assert(b == value);
}

但是术语访问是否包括更改?

是。

注:事实上,memcpy在概念上就是这么做的。它将字节修改为窄字符对象。如果这不可能,那么memcpy就无法在标准c++中实现。

但是陷阱表示呢?我们如何才能避免这种情况?

这很棘手。如果您知道陷阱表示,那么在尝试使用具有陷阱表示的类型中的值之前,您必须使用对象的窄字符视图来测试它。我不知道是否有任何标准的方法来处理陷阱表示。

也许应该有一个std::is_trap<T>(void*)特性来解决这个问题,但据我所知,还没有。

我知道另一种(更符合要求的?)方法是将uint8_t值转移到uint_t对象中。我们仍然必须遵守endianness,但这应该可以安全地省略陷阱表示。

如果外部值是陷阱表示,那么该值可能无论如何都不可表示,因此这种移位可能会出现其他问题,例如在这种情况下溢出。

shift和memcpy之间的区别在于,shift允许将已知的endianness转换为本机endianness,而memcpy在源已经具有本机endianness时工作。


如果保证uint8_tunsigned char的别名,那么第二个片段将被很好地定义,并且在功能上等同于memcpy。我不知道它是否有保证,但它肯定很常见。只有窄字符类型具有指针别名规则的例外。


assert(s[0] == (value & 0xff));

此断言依赖于cpu的端序。