C 中正好一个字节的位掩码

问题描述

我的目标是在四个字节中保存一个 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;

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

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只占八位,不一定等于一个字节。有没有办法让上层代码完全可移植到所有平台?

解决方法

怎么样:

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
,

我几乎只在嵌入式系统上工作,我经常不得不在各种或多或少的奇特系统之间提供可移植的代码。就像编写可以在一些微型 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 编译器的情况下,它不会实现 {{1}因为如果目标不支持它,它不是强制类型。只有 uint8_tuint_least8_t 是必需的。

然后你会这样做:

uint_fast8_t

奇特的 DSP 将使用 #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){ ... } 函数。您可以使用相同的编译器 uint32_to_uint16 检查来执行 #if CHAR_BIT 等。

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

,

关于:

unsigned long mask = (unsigned char)-1;

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

中说

1 当一个整数类型的值转换为另一个整数类型时 除了_Bool,如果该值可以用新类型表示,它 不变。

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

并且 unsigned long 可以表示 unsigned char 的所有值。

,
#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]);
}