将指向 uint16_t 的指针传递给需要 C 中 uint8_t[] 数组的子例程 - 如何?

Posted

技术标签:

【中文标题】将指向 uint16_t 的指针传递给需要 C 中 uint8_t[] 数组的子例程 - 如何?【英文标题】:Passing pointer to uint16_t to subroutine that expects uint8_t[] array in C - how to? 【发布时间】:2021-08-08 09:20:22 【问题描述】:

我有一个需要两个参数的子例程:uint8_t[] 数组缓冲区和 uint16_t 大小:void getData(uint8_t * pData, uint16_t size) 我想将指针传递给 uint16_t 变量,而不是 2 字节数组指针,这样我以后不必将数组内容转换为 uint16_t 变量。这是我尝试过的:

uint16_t value;
uint8_t values[2];
// instead of:
getData(values, 2);
value = (values[0] << 8) | values[1];
// I want to simply call:
getData((uint8_t *)&value, 2);

上述方法不会产生预期的结果。正确的方法是什么? 请指教。

【问题讨论】:

结果将取决于您机器的字节序。你写的内容对应于大端,但你很可能在小端架构上。 现在大多数 CPU 都是 little-endian(这意味着最低有效字节在前)。您对value 的计算会交换字节。 你能解释一下你想要的代码有什么问题吗?它看起来比你说你想要的可怕演员阵容好多 一如既往,如果您向我们提供输入和预期的输出/结果,将会有所帮助。 另外,如果您不遵循 eerorika 的建议,请使用 htons/ntohs 函数(here Windows 文档)。 【参考方案1】:
getData((uint8_t *)&value, 2);

上述方法没有产生预期的结果。

问题在于不同的计算机系统对多字节整数使用不同的顺序。您的第一个示例将数据解释为最低地址中的最高有效字节和最高地址中的最低有效字节。这称为“大端”字节顺序(大端优先)。

如果您的第二次尝试产生了不同的结果,那么我们可以推断您的系统使用了另一个顺序,“小端”。这其实很典型。例如,x86 CPU 架构使用 little endian 字节顺序。

正确的方法是什么?

将字节序列解释为大端整数的正确且可移植的方法是您不想这样做的:

// instead of:
getData(values, 2);
value = (values[0] << 8) | values[1];

您想要做的方式仅适用于将字节序列解释为“本机”字节顺序中的整数。在本机字节顺序不是大端的系统上,这是一种不同的操作。此操作在不同字节序的系统上表现不同,因此不可移植。

【讨论】:

感谢您的所有友好回答。事实上,字节顺序是我预期的方法不起作用的原因。我没想到。【参考方案2】:

两个版本的代码都依赖于字节序。如果您假设它总是在 little-endian 机器上工作,那么原始方法是有效的。在大端平台上,第二种方法有效,因为多字节值的内存设置与数学符号匹配。

为什么使用大端顺序通过网络和各种硬件接口进行通信是有简单的原因。

许多平台都包含“endian.h”标头,其中可以包含宏定义,以检测您的 CPU 或内存控制器是大端、小端还是“中端”(可变或异常顺序)。

对于在“winsock.h”中具有该定义的 Microsoft 编译器和 POSIX 兼容编译器之间的交叉线,我倾向于使用以下 sn-p:

#ifdef _WIN32
#include <windows.h>   // or appropriate winsockX.h
#   ifndef __LITTLE_ENDIAN
#       define __LITTLE_ENDIAN 1234
#   endif
#   ifndef __BYTE_ORDER
#       define __BYTE_ORDER __LITTLE_ENDIAN
#   endif
#else
#include <netinet/in.h>
#include <endian.h>
#   if __BYTE_ORDER != __LITTLE_ENDIAN
#       if __BYTE_ORDER != __BIG_ENDIAN
#           define __UNKNOWN_ENDIAN
#       endif
#   endif
#endif

然后我会将 __BYTE_ORDER 用于以下内容,但可能会被某种函数包裹为“实现细节”。

#if ((__BYTE_ORDER == __LITTLE_ENDIAN)) 
  getData(values, 2);
  value = (values[0] << 8) | values[1];

#else
  getData((uint8_t *)&value, 2);
#endif

【讨论】:

第一个版本不依赖于系统的字节序。它在大端系统上的工作方式与在小端系统上完全相同。不需要两个版本。 @eerorika 我想邀请读者仔细思考这个事实,以便真正理解它,因为它与 C 的核心原理和概念有关:硬件表示的抽象使得程序可移植,C 的目标之一。关键的见解是位移运算符是一种逻辑的、语义的 移位。左移不一定是向更小的地址移动:我们不知道它朝哪个方向移动,我们也不想知道。 编译器知道,并且做正确的事。 0x1234 很可能是内存中的 '0x34, 0x12`;与否。 没错,字节序是这里的问题。谢谢。

以上是关于将指向 uint16_t 的指针传递给需要 C 中 uint8_t[] 数组的子例程 - 如何?的主要内容,如果未能解决你的问题,请参考以下文章

c语法将const指针传递给const数据到函数

将 uint8_t 数组转换为 C 中的 uint16_t 值

从uint32_t [16]数组到uint32_t变量序列的64位副本

以便携方式检索传递给variadic函数的int32_t

为啥 uint8_t 在分配给取消引用的 uint32_t 指针时使用了 4 个字节?

在 C 语言中:我可以让两个不同类型的指针指向同一个地址吗?