问题描述
假设我有两个数组。
uint8_t[SIZE] src = { 0 };
uint32_t[SIZE] dst = { 0 };
uint8_t* srcPtr; // Points to current src value
uint32_t* dstPtr; // Points to current dst value
src
保存有时需要放入 dst
的值。重要的是,来自 src 的值可能是 8 位、16 位或 32 位,并且不一定正确对齐。因此,假设我希望使用如下所示的 memcpy() 来复制 16 位值
memcpy(dstPtr,srcPtr,2);
我会在这里遇到字节序问题吗?这在小端系统上工作正常,因为如果我想复制 8,那么 srcPtr 有 08
然后 00
dstPtr 的字节将是 08 00 00 00
并且值将是 8,如预期。
但是如果我在一个大端系统上,srcPtr 将是 00
然后是 08
,并且 dstPtr 的字节将是 00 08 00 00
(我认为),这将采取值为 524288。
编写此副本的字节序独立方式是什么?
解决方法
我会在这里遇到字节序问题吗?
是的。您不是在复制,而是从一种格式转换为另一种格式(将几个无符号整数打包成一个更大的无符号整数)。
编写此副本的字节序独立方式是什么?
简单的方法是使转换显式,例如:
for(int i = 0; i < something; i++) {
dest[i] = (uint32_t)src[i*4] | ((uint32_t)src[i*4+1] << 8) |
((uint32_t)src[i*4+2] << 16) | ((uint32_t)src[i*4+3] << 24);
}
但是,对于使用 memcpy()
的情况,它可能会更快,并且编译后不会改变;所以你可以做这样的事情:
#ifdef BIG_ENDIAN
for(int i = 0; i < something; i++) {
dest[i] = (uint32_t)src[i*4] | ((uint32_t)src[i*4+1] << 8) |
((uint32_t)src[i*4+2] << 16) | ((uint32_t)src[i*4+3] << 24);
}
#else
memcpy(dest,src,something*4);
#endif
注意:您还必须在适当的时候定义 BIG_ENDIAN
宏 - 例如当您知道目标架构需要它时,可能在启动编译器时使用 -D BIG_ENDIAN
命令行参数。
我在 src 中存储不是 16 位对齐的 16 位值,然后需要将其放入 64 位整数
这又增加了一个问题——某些架构不允许未对齐的访问。您也需要使用显式转换(读取 2 个单独的 uint8_t
,而不是未对齐的 uint16_t
)来避免此问题。
我会在这里遇到字节序问题吗?
不一定是字节序问题本身,但是是的,您描述的特定方法会遇到整数表示的问题。
这适用于 小端系统,因为如果我想复制 8,那么 srcPtr 有 08 那么 00 dstPtr 处的字节将是 08 00 00 00 并且该值将是 8,正如预期的那样。
您似乎在那里做出假设
- 目标的更多字节将被修改,而不是您实际复制,或者
- 目的地的相关部分已预先设置为全零字节。
但您需要了解 memcpy()
将准确复制请求的字节数。不会从指定的源读取更多的内容,并且不会在目标中修改超过的内容。尤其是源指针和目的指针指向的对象的数据类型对memcpy()
的操作没有影响。
编写此副本的字节序独立方式是什么?
最自然的方法是通过简单的赋值,依靠编译器来执行必要的转换:
*dstPtr = *srcPtr;
但是,我认为您强调数组可能未对齐的前景,因为您担心取消引用源和/或目标指针可能不安全。实际上,对于指向 char
的指针来说不是这种情况,但对于指向其他类型的指针来说可能是这种情况。对于将 memcpy
作为从数组中读取的唯一安全方法的情况,转换值表示的最便携方法仍然依赖于实现。例如:
uint8_t* srcPtr = /* ... */;
uint32_t* dstPtr = /* ... */;
uint16_t srcVal;
uint32_t dstVal;
memcpy(&srcVal,srcPtr,sizeof(srcVal));
dstVal = srcVal; // conversion is automatically performed
memcpy(dstPtr,&dstVal,sizeof(dstVal));