【问题标题】:Why Doesn't A Character Array Give an Unsigned Result为什么字符数组不给出无符号结果
【发布时间】:2020-09-03 16:20:59
【问题描述】:

在这个项目中,我应该接收一个数据包,并将其中的一部分转换为一个无符号整数,并获得 Big-Endian 和 Little-Endian 结果。最初,我只想将字节数组(数据包)内的指针转换为无符号整数类型,该类型会自动将接收到的值放入 Big-Endian 形式,例如 (uint32_be_t*)packet;,类似于它自动放入 Little-Endian 的方式做(uint32_t*)packet时的形式。

由于找不到自动执行此操作的类型,因此我决定创建自己的结构,称为“u32”,它具有“get”方法,该方法以 Big-Endian 形式获取值,以及“get_le”它以 Little-Endian 形式获取值。但是,我注意到当我这样做时,我会从 Little-Endian 结果中得到一个否定的结果。

struct u32 {
    u8 data[4] = {};

    uint32_t get() {
        return ((uint32_t)data[3] << 0)
            | ((uint32_t)data[2] << 8)
            | ((uint32_t)data[1] << 16)
            | ((uint32_t)data[0] << 24);
    }
    
    uint32_t get_le() {
        return ((uint32_t)data[3] << 24)
            | ((uint32_t)data[2] << 16)
            | ((uint32_t)data[1] << 8)
            | ((uint32_t)data[0] << 0);
    }
};

为了模拟一个数据包,我只是创建了一个字符数组,然后像这样将一个 u32* 转换为它:

int main() {
    char ary[] = { 0x00, 0x00, 0x00, (char)0xF4 };
    u32* v = (u32*)ary;    
    printf("%d %d\n", v->get(), v->get_le());
    return 0;
}

然后我得到了结果:244 -201326592

为什么会这样? “get_le”的返回类型是 uint32_t,第一个函数“get”应该返回 Big-Endian 无符号整数,它执行正确。

顺便说一句,这只是一个突然出现在我脑海中的测试,所以我去图书馆在类之间测试它,但不幸的是,这意味着我必须使用在线编译器 (onlinegdb),但我认为它在 Visual Studio 中的工作方式相同。此外,如果您对如何改进我的代码有任何建议,我们将不胜感激。我正在使用 Visual Studio 2019,并且可以使用 cstdlib。

【问题讨论】:

  • char 具有实现定义的签名。如果您不希望它可能将其解释为有符号值,请显式使用 unsigned char
  • 您希望 %u 用于无符号整数,%d 用于有符号整数。
  • u8 -> 为什么不uint8_t
  • 你还用什么?我讨厌使用 cout 因为我必须在每个值之间放置一个
  • 这就是为什么没人喜欢 printf了。这是一个略带双曲线的陈述。不是每个人都讨厌printf()

标签: c++ arrays c


【解决方案1】:

好吧,我敢说你想在那个printf()格式字符串中使用%u而不是%d

%d 假定该值是有符号的,因此如果最高有效位是 1,则会得到一个减号。

【讨论】:

  • 我不知道这一点。我假设“%d”的意思是“数字”,因为它会以预期的形式输出任何值。我可能应该再看看 printf..
  • @BrandonWoolworth printf 的致命缺陷是它不知道其参数的类型。您需要非常小心地使用正确匹配的格式代码,包括任何修改标志。
【解决方案2】:

有一种更优雅的方式来完成相同的任务。只需使用 uint32_t 代替。您可以使用std::memcpychar 数组和uint32_t 之间进行转换,而不会调用未定义的行为。这也是std::bit_cast 所做的。将 char* 重新解释为 int* 是未定义的行为。这不是问题的原因,因为 MSVC 允许这样做,但这并不是真正可移植的。

std::memcpy 转换或指针转换将使用本机字节顺序进行,即小端或大端。 您可以使用内置函数在字节顺序之间进行转换。对于 MSVC,这将是:

_byteswap_ulong(x); // unsigned long is uint32_t on Windows

请参阅documentation of _byteswap_ulong。这将编译为仅一条 x86 bswap 指令,这对于您的一系列班次来说不太可能。这可以将性能提高 10 倍。如果你想要可移植的代码,GCC 和 clang 有 __builtin_bswap

您可以使用 std::endian 或者如果您没有 C++20,则可以使用 __BYTE_ORDER__ 宏来检测本机字节顺序。根据您的平台字节序,转换为 little-endian 或 big-endian 将只是什么都不做或执行字节交换。

#include <bit>
#include <cstring>
#include <cstdint>

uint32_t bswap(uint32_t x) {
    return _byteswap_ulong(x);
}

uint32_t to_be(uint32_t x) {
    return std::endian::native == std::endian::big ? x : bswap(x);
}

uint32_t to_le(uint32_t x) {
    return std::endian::native == std::endian::little ? x : bswap(x);
}

int main() {
    char ary[4] = { 0, 0, 0, (char) 0xF4 };
    uint32_t v;
    std::memcpy(&v, &ary, 4);
    
    printf("%u %u\n", to_be(v), to_le(v));
    return 0;
}

【讨论】:

  • 伙计,C++20 看起来很棒。我不断看到我喜欢使用的东西(即概念),但不幸的是,在 MSVC 中实现的东西还不够多,而且我不想只使用 C++20 来了解我不能使用的东西。也就是说,我很欣赏评论。我选择不走这条路线,因为我知道我正在接收数据包,所以它们以特定的顺序出现(不确定我是否可以检测到字节序),我不能使用这些宏,否则我会使用 winsock 方法( htons 等)。
猜你喜欢
  • 1970-01-01
  • 2013-02-25
  • 2014-08-02
  • 2015-06-15
  • 2012-06-20
  • 2020-06-02
  • 1970-01-01
  • 2023-03-08
  • 2010-10-20
相关资源
最近更新 更多