Go语言:位域和位压缩

13

C语言的位域提供了一种相当方便的方法,在结构体内部定义任意宽度的字段(暂且不管可移植性问题)。例如,下面是一个简单的结构体,其中包含一些字段和一个'flag':

#pragma pack(push,1)
struct my_chunk{

    unsigned short fieldA: 16;
    unsigned short fieldB: 15;
    unsigned short fieldC:  1;
};
#pragma pop()

添加#pragma语句可以将该结构打包成32位字(确保指针操作的my_chunk指针对齐,并节省空间)。

访问每个字段在语法上非常好:

struct my_chunk aChunk;
aChunk.fieldA = 3;
aChunk.fieldB = 2;
aChunk.fieldC = 1;

如果不使用编程语言的帮助,另一种解决方案相当丑陋,几乎变成了汇编代码。例如,一种解决方案是为要访问的每个字段创建位移宏:

#define FIELD_A  0xFF00
#define FIELD_B  0x00FE
#define FIELD_C  0x0001

#define get_field(p, f) ((*p)&f)
#define set_field(p, f, v) (*p) = (v<<f) + (*p)&(~f)

...
set_field(&my_chunk, FIELD_A, 12345);

如果我想在Go语言中使用"位域(bitfields)",最佳实践是什么?大致可以参考这个链接(更正式的说法)。

2个回答

14

请注意,他并不是真的要求使用汇编语言,而是指当您必须手动访问这些子字段时,它看起来像汇编语言,因为需要进行所有掩码/移位操作。 - Alexis Wilke

9

如果目标只是要有一个非常小的结构体,你可能会这样做:

package main

import "fmt"

type my_chunk uint32

func (c my_chunk) A() uint16 {
  return uint16((c & 0xffff0000) >> 16) 
}

func (c *my_chunk) SetA(a uint16) {
  v := uint32(*c)
  *c = my_chunk((v & 0xffff) | (uint32(a) << 16))
}

func main() {
  x := my_chunk(123)
  x.SetA(12)
  fmt.Println(x.A())
}

当前的6g / 8g版本中,您将看到一个包含约6条指令的getter函数调用,随着时间的推移,这些调用可能会被内联。


2
只是一点附加说明:我尝试使用一个包含uint16和uint32的结构体(而不仅仅是一个'chunk'变量),但不幸的是,go语言给出了Sizeof() = 8,而不是6. 除此之外,非常感谢你的答案,它非常有帮助。 - Rooke
2
是的,使用结构体时成员很可能会被按字对齐。如果您已经在使用 "unsafe",可以使用 [3]uint16,并使用 "(*uint32)(unsafe.Pointer(&val[1]))" 获取 uint32 部分,这避免了打包/解包的成本,但有点丑陋。 - Kyle C

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