Gew*_*ure 4 c unions bit-fields
我在 C 课程中被分配了以下作业:
我已经实现了解码8 字节长的 int 131809282883593 的分配,如下所示:
#include <stdio.h>
#include <string.h>
struct Message {
unsigned int hour : 5;
unsigned int minutes : 6;
unsigned int seconds : 6;
unsigned int day : 5;
unsigned int month : 4;
unsigned int year : 12;
unsigned long long int code : 26;
}; // 64 bit in total
union Msgdecode {
long long int datablob;
struct Message elems;
};
int main(void) {
long long int datablob = 131809282883593;
union Msgdecode m;
m.datablob = datablob;
printf("%d:%d:%d %d.%d.%d code:%lu\n", m.elems.hour, m.elems.minutes,
m.elems.seconds, m.elems.day, m.elems.month, m.elems.year,(long unsigned int) m.elems.code);
union Msgdecode m2;
m2.elems.hour = 9;
m2.elems.minutes = 0;
m2.elems.seconds = 0;
m2.elems.day = 30;
m2.elems.month = 5;
m2.elems.year = 2017;
m2.elems.code = 4195376;
printf("m2.datablob: should: 131809282883593 is: %lld\n", m2.datablob); //WHY does m2.datablob != m.datablob?!
printf("m.datablob: should: 131809282883593 is: %lld\n", m.datablob);
printf("%d:%d:%d %d.%d.%d code:%lu\n", m2.elems.hour, m2.elems.minutes,
m2.elems.seconds, m2.elems.day, m2.elems.month, m2.elems.year, (long unsigned int) m2.elems.code);
}
Run Code Online (Sandbox Code Playgroud)
..让我感到困难的是输出。到目前为止,解码/编码工作得很好。9:0:0 30.5.2017 和代码 4195376 是预期的,但“datablob”中的差异实际上不是 - 而且我无法弄清楚为什么/它源于何处:
9:0:0 30.5.2017 code:4195376
m2.datablob: should: 131809282883593 is: 131810088189961
m.datablob: should: 131809282883593 is: 131809282883593
9:0:0 30.5.2017 code:4195376
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,数据块接近原始数据 - 但不是原始数据。我曾就此问题咨询过一位精通 C 语言的同事,但我们无法找出这种行为的原因。
问:为什么斑点彼此不同?
Bonus-Q:当操纵联合体Msgdecode包含另一个字段时,会发生一件奇怪的事情:
union Msgdecode {
long long int datablob;
struct Message elems;
char bytes[8]; // added this
};
Run Code Online (Sandbox Code Playgroud)
结果:
9:0:0 30.5.2017 code:0
m2.datablob: should: 131809282883593 is: 8662973939721
m.datablob: should: 131809282883593 is: 131809282883593
9:0:0 30.5.2017 code:4195376
Run Code Online (Sandbox Code Playgroud)
PS:阅读有关位域+联合问题的SO给我的印象是它们相当不可靠。这可以笼统地说吗?
a 中位域的布局struct以及它们之间可能存在的任何填充都是实现定义的。
来自C 标准第 6.7.2.1 节:
11实现可以分配任何足够大以容纳位字段的可寻址存储单元。如果剩余足够的空间,则结构中紧跟在另一个位字段之后的位字段应被打包到同一单元的相邻位中。如果剩余空间不足,则不适合的位字段是否放入下一个单元或与相邻单元重叠是实现定义的。单元内位字段的分配顺序(高位到低位或低位到高位)是由实现定义的。可寻址存储单元的对齐方式未指定。
这意味着您不能以符合标准的方式依赖布局。
话虽这么说,让我们看看在这种特殊情况下这些位是如何布局的。重申一下,从这里开始的所有内容都属于实现定义的行为领域。我们将从第二种情况开始,其中m2.datablob8662973939721 因为这更容易解释。
首先让我们看看您分配给的值的位表示m2:
- hour: 9: 0 1001 (0x09)
- minutes: 0: 00 0000 (0x00)
- seconds: 0: 00 0000 (0x00)
- day: 30: 11 1110 (0x3E)
- month: 5: 0101 (0x05)
- year: 2017: 0111 1110 0001 (0x7e1)
- code: 4195376: 00 0100 0000 0000 0100 0011 0000 (0x0400430)
Run Code Online (Sandbox Code Playgroud)
现在让我们看看 blob 值,首先m分配给blobvalue然后m2使用上述值单独分配给每个字段:
131809282883593 0x77E13D7C0009 0111 0111 1110 0001
0011 1101 0111 1100 0000 0000 0000 1001
8662973939721 0x07E1017C0009 0000 0111 1110 0001
0000 0001 0111 1100 0000 0000 0000 1001
Run Code Online (Sandbox Code Playgroud)
如果我们首先从右向左查看值,我们可以看到值 9,所以这是我们的前 5 位。接下来是接下来两个字段的两组 6 个零位。之后,我们看到 30 的位模式,然后是 5。
再往上一点,我们看到值 2017 的位模式,但在该值和之前的值之间有 6 位设置为零。所以看起来布局如下:
year ??? month day sec min hour
------------ ----- --- ---- ------ ----- -----
| | | || || || || | |
0000 0111 1110 0001 0000 0001 0111 1100 0000 0000 0000 1001
Run Code Online (Sandbox Code Playgroud)
year因此和字段之间有一些填充month。比较m和 的m2表示形式,差异在于 和 之间的 6 位填充month以及year左侧的 4 位year。
我们在这里看不到的是该字段的位code。那么这个结构到底有多大呢?
如果我们将其添加到代码中:
printf("size = %zu\n", sizeof(struct Message));
Run Code Online (Sandbox Code Playgroud)
我们得到:
size = 16
Run Code Online (Sandbox Code Playgroud)
它比我们想象的要大得多。那么让我们创建bytes数组unsigned char [16]并输出它。代码:
int i;
printf("m: ");
for (i=0; i<16; i++) {
printf(" %02x", m.bytes[i]);
}
printf("\n");
printf("m2:");
for (i=0; i<16; i++) {
printf(" %02x", m2.bytes[i]);
}
printf("\n");
Run Code Online (Sandbox Code Playgroud)
输出:
printf("size = %zu\n", sizeof(struct Message));
Run Code Online (Sandbox Code Playgroud)
现在我们看到 0x0400430 位模式对应于 m2 表示中的代码字段。该字段之前有额外的 20 位填充。另请注意,字节的顺序与值的顺序相反,这表明我们在小端机器上。鉴于值的布局方式,每个字节中的位也可能是小端字节序。
那么为什么要填充呢?这很可能与对齐有关。前 5 个字段为 8 位或更少,这意味着它们每个都适合一个字节。单字节没有对齐要求,因此它们是打包的。下一个字段是 12 位,这意味着它需要适合 16 位(2 字节)字段。因此添加了 6 位填充,因此该字段以 2 字节偏移量开始。下一个字段是 26 位,需要 32 位字段。这意味着它需要从 4 字节偏移量开始并使用 4 字节,但是由于该字段已声明(unsigned long long在本例中为 8 字节),因此该字段使用 8 字节。如果您声明了此字段,unsigned int它可能仍会以相同的偏移量开始,但仅使用 4 个字节,而不是 8 个字节。
现在,第一种情况(blob 值为 131810088189961)又如何呢?让我们看看它的表示与“预期”的比较:
size = 16
Run Code Online (Sandbox Code Playgroud)
这两种表示在存储数据的位中具有相同的值。它们之间的区别在于month和year字段之间的 6 个填充位。至于为什么这种表示不同,编译器可能在意识到某些位不能或无法读取或写入时进行了一些优化。通过向联合添加char数组,可能会读取或写入这些位,从而无法再进行优化。
使用 gcc,您可以尝试__attribute((packed))在结构上使用。这样做会产生以下输出(在将bytes数组调整为 8 以及打印时的循环限制之后):
int i;
printf("m: ");
for (i=0; i<16; i++) {
printf(" %02x", m.bytes[i]);
}
printf("\n");
printf("m2:");
for (i=0; i<16; i++) {
printf(" %02x", m2.bytes[i]);
}
printf("\n");
Run Code Online (Sandbox Code Playgroud)
以及位表示:
m: 09 00 7c 3d e1 77 00 00 00 00 00 00 00 00 00 00
m2: 09 00 7c 01 e1 07 00 00 30 04 40 00 00 00 00 00
Run Code Online (Sandbox Code Playgroud)
但即便如此,您也可能会遇到问题。
总而言之,使用位域无法保证布局。您最好使用位移位和掩码来将值移入和移出位字段,而不是尝试覆盖它。
| 归档时间: |
|
| 查看次数: |
3329 次 |
| 最近记录: |