结构的第 16 个字节上的 16bit int 跳过一个字节
16bit int on 16th byte of structure skips a byte
在长于 16 字节的 c 结构中,如果它是 2 字节元素,第 16 个字节就会出现问题。让我用代码解释一下:
struct test{
unsigned int a : 16;
unsigned int b : 16;
unsigned int c : 16;
unsigned int d : 16;
unsigned int e : 16;
unsigned int f : 16;
unsigned int g : 16;
unsigned int h : 8;
unsigned int i : 16;
};
int main(int argc, char *argv[]){
//bytes 0, 1, 16, 17 are all equal to 0x31
unsigned char *data = "1134567890123451189";
struct test *testStructure = (struct test*) data;
printf("%X %X\n", testStructure->a, testStructure->i);
return 0;
}
输出:
3131 3831
为什么 'i' 不等于 'a'? 'i' 跳过字节 16 并使用字节 17 和 18 代替。这是怎么回事?
我不会这样做,因为:
- 位字段打包是实现定义的。参见 C99 §6.7.2.1p10:“单元内位字段的分配顺序(高阶到低阶或低阶到高阶)是实现定义的
- 这违反了严格的别名规则。
在这种情况下,实际发生的情况很可能是 i
对齐 4 个字节(int
的大小)。
您可以禁用对齐,它应该会给您带来更好的结果:
#pragma pack(1)
struct test{
unsigned int a : 16;
unsigned int b : 16;
unsigned int c : 16;
unsigned int d : 16;
unsigned int e : 16;
unsigned int f : 16;
unsigned int g : 16;
unsigned int h : 8;
unsigned int i : 16;
};
#pragma pack()
int main(int argc, char *argv[]){
//bytes 0, 1, 15, 16 are all equal to 0x31
unsigned char *data = "1134567890123451189";
struct test *testStructure = (struct test*) data;
printf("%X %X\n", testStructure->a, testStructure->i);
return 0;
}
在 clang 上,x86_64 它打印:
3131 3131
但是,此代码仍然非法,不能保证在任何地方都以这种方式工作。
要解决位域问题,请尽量不要使用位域(幸运的是在您的特定情况下这是可能的)。但不幸的是,aliasing 问题没有简单的解决方案;大多数依赖类型双关的人只需使用 -fno-strict-aliasing
进行编译(包括 Linux 内核人员)。其他人使用 union
s 跳过箍,严格来说这仍然是非法的,但却是常见的习语并且得到大多数编译器的良好支持:
#include <stdio.h>
#include <stdint.h>
#pragma pack(1)
struct test{
uint16_t a;
uint16_t b;
uint16_t c;
uint16_t d;
uint16_t e;
uint16_t f;
uint16_t g;
uint8_t h;
uint16_t i;
};
union u{
struct test t;
char str[17];
};
#pragma pack()
int main(int argc, char *argv[]){
//bytes 0, 1, 15, 16 are all equal to 0x31
char *data = "1134567890123451189";
union u *testStructure = (union u*) data;
printf("%X %X\n", testStructure->t.a, testStructure->t.i);
return 0;
}
在长于 16 字节的 c 结构中,如果它是 2 字节元素,第 16 个字节就会出现问题。让我用代码解释一下:
struct test{
unsigned int a : 16;
unsigned int b : 16;
unsigned int c : 16;
unsigned int d : 16;
unsigned int e : 16;
unsigned int f : 16;
unsigned int g : 16;
unsigned int h : 8;
unsigned int i : 16;
};
int main(int argc, char *argv[]){
//bytes 0, 1, 16, 17 are all equal to 0x31
unsigned char *data = "1134567890123451189";
struct test *testStructure = (struct test*) data;
printf("%X %X\n", testStructure->a, testStructure->i);
return 0;
}
输出:
3131 3831
为什么 'i' 不等于 'a'? 'i' 跳过字节 16 并使用字节 17 和 18 代替。这是怎么回事?
我不会这样做,因为:
- 位字段打包是实现定义的。参见 C99 §6.7.2.1p10:“单元内位字段的分配顺序(高阶到低阶或低阶到高阶)是实现定义的
- 这违反了严格的别名规则。
在这种情况下,实际发生的情况很可能是 i
对齐 4 个字节(int
的大小)。
您可以禁用对齐,它应该会给您带来更好的结果:
#pragma pack(1)
struct test{
unsigned int a : 16;
unsigned int b : 16;
unsigned int c : 16;
unsigned int d : 16;
unsigned int e : 16;
unsigned int f : 16;
unsigned int g : 16;
unsigned int h : 8;
unsigned int i : 16;
};
#pragma pack()
int main(int argc, char *argv[]){
//bytes 0, 1, 15, 16 are all equal to 0x31
unsigned char *data = "1134567890123451189";
struct test *testStructure = (struct test*) data;
printf("%X %X\n", testStructure->a, testStructure->i);
return 0;
}
在 clang 上,x86_64 它打印:
3131 3131
但是,此代码仍然非法,不能保证在任何地方都以这种方式工作。
要解决位域问题,请尽量不要使用位域(幸运的是在您的特定情况下这是可能的)。但不幸的是,aliasing 问题没有简单的解决方案;大多数依赖类型双关的人只需使用 -fno-strict-aliasing
进行编译(包括 Linux 内核人员)。其他人使用 union
s 跳过箍,严格来说这仍然是非法的,但却是常见的习语并且得到大多数编译器的良好支持:
#include <stdio.h>
#include <stdint.h>
#pragma pack(1)
struct test{
uint16_t a;
uint16_t b;
uint16_t c;
uint16_t d;
uint16_t e;
uint16_t f;
uint16_t g;
uint8_t h;
uint16_t i;
};
union u{
struct test t;
char str[17];
};
#pragma pack()
int main(int argc, char *argv[]){
//bytes 0, 1, 15, 16 are all equal to 0x31
char *data = "1134567890123451189";
union u *testStructure = (union u*) data;
printf("%X %X\n", testStructure->t.a, testStructure->t.i);
return 0;
}