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



我的目标是用四个字节保存一个long,如下所示:

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只占八个比特,这不一定等同于一个字节。有没有一种方法可以使上层代码完全可移植到所有平台?

像这样的东西怎么样

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

然后用它作为掩模而不是CCD_ 5?

测试程序:

#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("%lxn", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_9;
        mask-= 1;
        printf("%lxn", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_10;
        mask-= 1;
        printf("%lxn", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_11;
        mask-= 1;
        printf("%lxn", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_12;
        mask-= 1;
        printf("%lxn", mask);
    }
}

输出:

ff
1ff
3ff
7ff
fff

我几乎只与嵌入式系统合作,在那里我经常不得不在各种不同的系统之间提供可移植的代码。就像写代码一样,它既可以在一些小的8位MCU上工作,也可以在x8_64上工作。

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

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

如果可移植性是一个实际问题,那么您必须使用stdint.h。然后,如果你真的必须支持异国情调的系统,你必须决定哪一个。据我所知,现实世界中唯一使用不同字节大小的计算机是20世纪90年代各种过时的奇异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

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

关于:

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%xn", 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%02xn", bytes[0], bytes[1], bytes[2], bytes[3]);
}

最新更新