【问题标题】:Bitmask for exactly one byte in CC中恰好一个字节的位掩码
【发布时间】:2021-07-23 05:06:58
【问题描述】:

我的目标是将long 保存为四个字节like this

unsigned char bytes[4];
unsigned long n = 123;

bytes[0] = (n >> 24) & 0xFF;
bytes[1] = (n >> 16) & 0xFF;
bytes[2] = (n >> 8) & 0xFF;
bytes[3] = n & 0xFF;

但我希望代码是可移植的,所以我使用来自<limits.h>CHAR_BIT

unsigned char bytes[4];
unsigned long n = 123;

bytes[0] = (n >> (CHAR_BIT * 3)) & 0xFF;
bytes[1] = (n >> (CHAR_BIT * 2)) & 0xFF;
bytes[2] = (n >> CHAR_BIT) & 0xFF;
bytes[3] = n & 0xFF;

问题是位掩码0xFF只占8位,不一定等于1个字节。有没有办法让上层代码在所有平台上完全可移植?

【问题讨论】:

  • 我想我遗漏了一些东西,但为什么第一个不便携?
  • @efox29 有些架构的字节大于 8 位。
  • 此外,不能保证long 由4 个字节组成。所以你试图解决的任务是不可移植的:)
  • 很有趣……但这有什么关系吗?您将值存储在无符号字符(可能是也可能不是 8 位)中。为什么不使用 stdint.h 来获取指定大小的 uint8_t ?
  • 解决问题的最佳方法是_Static_assert(CHAR_BITS==8, "Don't import C libraries into your horrible exotic DSP project. And consider getting a better CPU.");

标签: c char byte bit bitmask


【解决方案1】:

怎么样:

unsigned long mask = 1;
mask<<=CHAR_BIT;
mask-=1;

然后使用它作为掩码而不是0xFF?

测试程序:

#include <stdio.h>

int main() {
    #define MY_CHAR_BIT_8 8
    #define MY_CHAR_BIT_9 9
    #define MY_CHAR_BIT_10 10
    #define MY_CHAR_BIT_11 11
    #define MY_CHAR_BIT_12 12
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_8;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_9;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_10;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_11;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_12;
        mask-= 1;
        printf("%lx\n", mask);
    }
}

Output:

ff
1ff
3ff
7ff
fff

【讨论】:

  • 远胜于循环
  • 我建议使用unsigned long,而不是uint32_t,因为掩码的类型应该与序列化类型相同。
  • @tstanisl 我已经编辑了答案。感谢您的意见!
  • 将格式说明符也更新为"%lx\n" :)
  • @tstanisl 完成!
【解决方案2】:

我几乎只在嵌入式系统上工作,我经常不得不在各种或多或少的奇异系统之间提供可移植的代码。就像编写可以在一些微型 8 位 MCU 和 x86_64 上运行的代码一样。

但即使对我来说,为移植到异国情调的过时 DSP 系统等而烦恼也是一种巨大的时间浪费。这些系统在现实世界中几乎不存在 - 为什么您需要对它们进行移植?除了“炫耀”大部分无用的C语言律师知识之外,还有其他原因吗?根据我的经验,99% 的此类无用可移植性问题归结为程序员“炫耀”,而不是实际的需求规范。

即使您出于某种奇怪的原因确实需要这种可移植性,但开始这项任务也没有任何意义,因为 charlong 都不是可移植的!如果 char 不是 8 位,那么是什么让您认为 long 是 4 字节?可以是 2 个字节,也可以是 8 个字节,也可以是别的。

如果可移植性是一个实际问题,那么您必须使用stdint.h。然后,如果您真的必须支持外来系统,则必须决定哪些系统。我所知道的唯一实际使用不同字节大小的真实计算机是 1990 年代各种过时的奇异 TI DSP,它们使用 16 位字节/字符。让我们假设这是您决定支持的重要目标。

我们还假设存在用于该奇异目标的标准 C 编译器 (ISO 9899),这是极不可能的。 (更有可能你会得到一个不符合标准的,大部分被破坏的遗留 C90 东西......甚至更有可能那些使用目标的人在汇编程序中编写所有内容。)如果是标准 C 编译器,它将不会实现 uint8_t,因为如果目标不支持它,它不是强制类型。只有uint_least8_tuint_fast8_t 是强制性的。

然后你会这样去做:

#include <stdint.h>
#include <limits.h>
#if CHAR_BIT == 8
static void uint32_to_uint8 (uint8_t dst[4], uint32_t u32)
{
  dst[0] = (u32 >> 24) & 0xFF;
  dst[1] = (u32 >> 16) & 0xFF;
  dst[2] = (u32 >>  8) & 0xFF;
  dst[3] = (u32 >>  0) & 0xFF;
}
#endif 

// whatever other conversion functions you need:
static void uint32_to_uint16 (uint16_t dst[2], uint32_t u32){ ... }
static void uint64_to_uint16 (uint16_t dst[2], uint32_t u32){ ... }

然后,奇异的 DSP 将使用 uint32_to_uint16 函数。您可以使用相同的编译器 #if CHAR_BIT 检查执行 #define byte_to_word uint32_to_uint16 等。

然后还应该立即注意到字节序将是下一个主要的可移植性问题。我不知道过时的 DSP 经常使用什么字节序,但这是另一个问题。

【讨论】:

  • 你可能是对的。我应该开始关注编程的更有趣的方面,而不是为每个平台的可移植性而烦恼。感谢您的回答!
【解决方案3】:

怎么样:

unsigned long mask = (unsigned char)-1;

这会起作用,因为 C 标准在 6.3.1.3p2 中说

1 当一个整数类型的值被转换为另一个整数类型时 除 _Bool 以外,如果该值可以用新类型表示,则 没有改变。

2 否则,如果新类型是无符号的,则将值转换为 反复加或减一大于最大值 可以用新类型表示,直到值在 新类型。

unsigned long 可以代表unsigned char 的所有值。

【讨论】:

  • 不可移植,因为它假定二进制补码。他要求便携方式
  • @0___________,2 补码不是用于编码 有符号 整数吗?
  • @0___________,cast 是不是重新解释,它会将mask 设置为“比最大值大一”,即 2^CHAR_BIT 增加-1。它实际上是将所有值位设置为无符号整数的最便携方式。
  • 在某些系统上是,在另一个系统上不是。 便携意味着它可以在任何系统上工作。
  • ~0 是最便携的方式
【解决方案4】:
#define CHARMASK ((1UL << CHAR_BIT) - 1)

int main(void)
{
    printf("0x%x\n", CHARMASK);
}

并且掩码将始终具有字符的宽度。计算编译时间,不需要额外的变量。

或者

#define CHARMASK    ((unsigned char)(~0))

不戴面具也可以

void foo(unsigned int n, unsigned char *bytes)
{
    bytes[0] = ((n << (CHAR_BIT * 0)) >> (CHAR_BIT * 3));
    bytes[1] = ((n << (CHAR_BIT * 1)) >> (CHAR_BIT * 3));
    bytes[2] = ((n << (CHAR_BIT * 2)) >> (CHAR_BIT * 3));
    bytes[3] = ((n << (CHAR_BIT * 3)) >> (CHAR_BIT * 3));
}


int main(void)
{
    unsigned int z = 0xaabbccdd;
    unsigned char bytes[4];
    foo(z, bytes);
    printf("0x%02x 0x%02x 0x%02x 0x%02x\n", bytes[0], bytes[1], bytes[2], bytes[3]);
}

【讨论】:

  • ((unsigned char)(~0)) 不可移植。在具有 int ~0 的“一个补码”表示的机器上将是 -0!转换为无符号类型会将掩码设置为 0,这是非常错误的。请阅读en.wikipedia.org/wiki/Ones%27_complement
  • 更重要的是,假设 long/int 会很好地保持 4x8 位整数而 char 是其他东西是不正确的。
猜你喜欢
  • 1970-01-01
  • 2021-07-15
  • 2022-01-18
  • 2010-09-23
  • 2010-12-02
  • 2011-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多