如何在C语言中表示比特信息?

5

我需要在C语言中存储0-15之间的值,只需4位即可。我该如何创建一个只有4位的变量呢?由于空间有限制,请帮我优化一下。

6个回答

7
考虑使用char。是的,它只有8位,但您可以使用位移运算符(<<>>)将值存储在其余4位中。 编辑:根据下面的评论,unsigned char实际上比char更可取,以避免签名位问题。

你可以使用位运算符将两个4位值存储在char中。但是,你应该确保使用unsigned char,否则在进行有符号移位时会出现奇怪的行为。 - Chris Lutz

4

你可以使用位域来存储你的4位数据,但是,除非你在一个结构体中有多个相邻的位域,否则与将值存储在字节中相比,你不会节省任何空间。


1
即使您将它们放在相邻的位置,编译器也不能保证会在内存中对齐它们并以这种方式打包数据结构。即使您尝试,大多数现代编译器也不会特别地以这种方式打包它们的结构。有关更多详细信息,请参见我的答案。 - Simon
此外,没有办法在不使用条件语句的情况下访问位域。如果您自己将打包操作转换为 unsigned char,那么您可以简单地使用索引的低位比特作为移位操作数,例如 (i&1)<<2 - R.. GitHub STOP HELPING ICE

3

你不能真正拥有一个4位变量,但是你可以拥有存储两个4位值的8位变量,但是你必须使用一个临时变量来访问它们,这意味着除非你有超过两个变量,否则你不会节省任何空间:

uint8_t var_both;
uint8_t temp = (var_both >> 4) & 0x0F; // For first value
temp = var_both & 0x0F; // For second value

2
正如Chris Lutz所指出的那样,您可以通过添加冒号和其大小来定义变量使用的位数:unsigned char myOneBitVariable:1;对于您的情况是'unsigned char MyFourBitVariable:4'。我想指出这是非常困难的,并且为什么您应该避免它。
大多数现代编译器都会对结构中变量的空间进行对齐。今天最普遍的情况是4字节或甚至8字节,但这因平台和编译器而异。一些编译器允许您指定数据及其成员的对齐方式。在GCC上,关键字是__attribute__((aligned(x))),在MSVC上则是__declspec(align(x))。在大多数情况下,您还需要指定编译器应该打包结构的大小。MSVC有#pragma pack(x)指令:http://msdn.microsoft.com/en-us/library/2e70t5y1(VS.80).aspx。您也可以阅读有关MSVC对齐的信息:http://msdn.microsoft.com/en-us/library/83ythb65(VS.80).aspx。GCC有自己的实现,称为__attribute__ ((__packed__),您可能需要搜索一下。
以下是使用Microsoft编译器无法得到所需结果的示例:

#ifndef _MSC_VER
#error This alignment solution / packing solution is only valid on MSC
#endif /* ifndef _MSC_VER */

#define M_ALIGN(x)    __declspec(align(x))

struct S64Bits
{
    unsigned char MyOneBitVariable:1;
    int My32BitInt;
};

// MSVC specific implementation of data-packing in a type.
#pragma pack(1)
struct S32Bits
{
    D_ALIGN(1) int My16BitVariable:16;
    D_ALIGN(1) unsigned char Padding8Bits;
    D_ALIGN(1) unsigned char MyOneBitVariable1:1;
    D_ALIGN(1) unsigned char MyOneBitVariable2:1;
    D_ALIGN(1) unsigned char MyOneBitVariable3:1;
    D_ALIGN(1) unsigned char MyOneBitVariable4:1;
    D_ALIGN(1) unsigned char MyFourBitVariable:4;
};
#pragma pack(pop)

'sizeof(S64Bits)'应该为8,确实如此。'sizeof(S32Bits)'应该是4,但事实并非如此。在MSVC上,后者是6个字节。这种行为还与编译器有关,并且通常具有编译器唯一的指令。这种行为几乎从不会给你想要的结果。我经常使用宏来确保我需要的结构确实是某个特定大小:


#define TEST_TYPE_SIZE(Type, Size) assert(sizeof(Type) == Size);

我将在下面使用所有数据类型,以尝试指定它们的确切大小。但是,依靠结构体的任何大小(而不是sizeof(mystructure))都可能导致难以调试的错误。最好使用对齐编译器指令来将数据对齐到缓存行大小和类似效率问题。Karl Bielefeldt提供了一种将4位值存储到uint8中的良好自然解决方案,使用它们代替。

1
非可移植的黑客技巧?我明确说明了关于编译器特定性的问题。当涉及到以某种方式打包数据时,了解每个编译器如何打包和对齐数据非常重要。问题是如何仅使用4位,因为空间是一个限制,我试图提供有关获取所需内存大小的复杂性的信息。您能否详细说明一下您的批评意见? - Simon
@R..:也许你没有看到我关于使用这些方法实现正确大小的困难的评论?或者你没有看到我关于依赖结构大小的文本?我认为它们很好地解释了为什么不应该使用这种解决方案。我的论点并不是支持使用这些解决方案,而是反对它。 - Simon
@R..:我修改了帖子,希望现在更清晰了。不过我仍然无法将那些编译指示变为普通文本。感谢额外的反馈。 - Simon
删除了-1。我仍然认为这不是一个非常好的答案,但也不会误导/有害。我建议的另一个更改是明确说明您在使用编译器扩展和其他不可移植的东西的位置。 - R.. GitHub STOP HELPING ICE
@R..:关于编译器扩展和类似问题,我添加了更多的澄清说明 :) - Simon
显示剩余2条评论

0
半字节的术语是“nibble”。 因此,在这里:
struct two_nibbles {
  unsigned a :4;
  unsigned b :4;
}

你必须将两个变量命名为x.ax.b(但将x更改为任何名称),这样可以节省一些空间。不过,你可能需要检查一下 - 我认为编译器会确保sizeof(struct two_nibbles) == sizeof(char),但也可能不会,因此你可能需要添加更多的nibbles以使其占用更多空间。


通过将a和b定义为无符号,它们会自动成为整数,因此占用4个或更多字节? - Laz
可以使用Chris在此处显示的符号来指定结构体中无符号整型变量的宽度。上述示例中的每个字段a和b将使用4位。但是请记住,编译器可能会在这种结构体的末尾添加填充。 - gnud
编译器在实践中会将它们变成4个字节。你应该使用 "unsigned char a:4; unsigned char b:4;" 或更好的是,抛弃位域(C语言中最愚蠢、最没用的东西之一),自己进行位运算,这样可以得到明确定义的行为。 - R.. GitHub STOP HELPING ICE

0

你是否会想要获取这些4位值的地址?如果是,你需要将它们存储在“适当”的数据类型中,例如char。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接