今天,我在尝试使用位字段时发现了令人担忧的行为.为了便于讨论和简化,这是一个示例程序:
#include <stdio.h>
struct Node
{
int a:16 __attribute__ ((packed));
int b:16 __attribute__ ((packed));
unsigned int c:27 __attribute__ ((packed));
unsigned int d:3 __attribute__ ((packed));
unsigned int e:2 __attribute__ ((packed));
};
int main (int argc, char *argv[])
{
Node n;
n.a = 12345;
n.b = -23456;
n.c = 0x7ffffff;
n.d = 0x7;
n.e = 0x3;
printf("3-bit field cast to int: %d\n",(int)n.d);
n.d++;
printf("3-bit field cast to int: %d\n",(int)n.d);
}
Run Code Online (Sandbox Code Playgroud)
该程序故意导致3位位域溢出.这是使用"g ++ -O0"编译时的(正确)输出:
3位字段转换为int:7
3位字段转换为int:0
这是使用"g ++ -O2"(和-O3)编译时的输出:
3位字段转换为int:7
3位字段转换为int:8
检查后一个例子的程序集,我发现了这个:
movl …Run Code Online (Sandbox Code Playgroud) 我使用了具有这样结构的位字段,
struct
{
unsigned int is_static: 1;
unsigned int is_extern: 1;
unsigned int is_auto: 1;
} flags;
Run Code Online (Sandbox Code Playgroud)
现在我想知道是否可以通过联合完成这样我修改代码,如,
union
{
unsigned int is_static: 1;
unsigned int is_extern: 1;
unsigned int is_auto: 1;
} flags;
Run Code Online (Sandbox Code Playgroud)
我找到了带有union的bit字段,但是从输出中可以理解,union中的所有字段都被赋予了一个位.现在我看到使用带有union的位字段并不是错误的,但在我看来,像这样使用它在操作上是不正确的.那么答案是什么 - 将bit字段与union结合使用是否有效?
我可以在C或C++的位字段中指定的位数是否有限制?例如,我可以这样做:
struct HugeInt {
int myInt: 1000;
};
Run Code Online (Sandbox Code Playgroud)
我问的是C和C++,因为我知道语言规范有时会有所不同,并且想看看上面的例子是否保证在C或C++中工作/不工作.
C++ 11中的第9.6/3节非常清楚:"非const引用不应绑定到位字段." 这项禁令背后的动机是什么?
我知道不可能将引用直接绑定到位域.但如果我宣布这样的话,
struct IPv4Header {
std::uint32_t version:4, // assumes the IPv4 Wikipedia entry is correct
IHL:4,
DSCP:6,
ECN:2,
totalLength:16;
};
Run Code Online (Sandbox Code Playgroud)
为什么我不能这样说呢?
IPv4Header h;
auto& ecn = h.ECN;
Run Code Online (Sandbox Code Playgroud)
我希望底层代码实际绑定到std::uint32_t包含我感兴趣的位的整个代码,并且我希望读取和写入操作生成代码以进行适当的屏蔽.结果可能是大而慢,但在我看来它应该工作.这与标准表示对const位域的引用有效的方式一致(同样来自9.6/3):
如果类型为const T&的引用的初始值设定项是引用位字段的左值,则引用绑定到初始化的临时值以保存位字段的值; 引用不直接绑定到位字段.
这表明写入位域是问题,但我不知道它是什么.我认为必要的屏蔽可能会在多线程代码中引入竞争,但是,根据1.7/3,非零宽度的相邻位域被认为是用于多线程的单个对象.在上面的示例中,IPv4Header对象中的所有位域都将被视为单个对象,因此根据定义,在读取其他字段时尝试修改字段的多线程代码已经很有效.
我显然遗漏了一些东西.它是什么?
我正在为Cortex-M0 CPU和gcc编写代码.我有以下结构:
struct {
volatile unsigned flag1: 1;
unsigned flag2: 1;
unsigned foo; // something else accessed in main loop
} flags;
Run Code Online (Sandbox Code Playgroud)
flag1从GPIO中断处理程序和主循环读取和写入.flag2只在主循环中读写.
ISR看起来像这样:
void handleIRQ(void) {
if (!flags.flag1) {
flags.flag1 = 1;
// enable some hw timer
}
}
Run Code Online (Sandbox Code Playgroud)
主循环看起来像这样:
for (;;) {
// disable IRQ
if (flags.flag1) {
// handle IRQ
flags.flag1 = 0;
// access (rw) flag2 many times
}
// wait for interrupt, enable IRQ
}
Run Code Online (Sandbox Code Playgroud)
flag2在主循环中访问时,编译器是否会优化对它的访问,以便每次在代码中读取或写入时都不会将其提取或存储到内存中?
我不清楚因为要设置flag1ISR,它需要加载整数char,设置一下并存储回来.
-Wconversion 当我用g ++为一个位字段赋值时,会产生警告.
源文件:
struct Foo
{
public:
unsigned int x : 4;
unsigned int y : 9;
unsigned int z : 17;
};
int main(int, char**)
{
int a = 12;
Foo f;
f.x = a;
f.x = (unsigned int)a;
f.x = (unsigned char)a;
f.x = (unsigned short)a;
f.x = (unsigned)a;
f.y = a;
f.y = (unsigned int)a;
f.y = (unsigned char)a; // no warning, sizeof(char) < 9
f.y = (unsigned short)a;
f.y = (unsigned)a;
f.z = a;
f.z …Run Code Online (Sandbox Code Playgroud) 在C11中工作,结构如下:
struct S {
unsigned a : 4;
_Bool b : 1;
};
Run Code Online (Sandbox Code Playgroud)
由GCC布局为unsigned(4字节),其中使用4位,然后是_Bool(4字节),其中使用1位,总大小为8字节.
注意,C99和C11特别允许_Bool作为位字段成员.C11标准(也可能是C99)也在§6.7.2.1"结构和联合说明符"中声明:
实现可以分配任何足够大的可寻址存储单元来保存位字段.如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中.
所以我认为b上面的成员应该被打包到为成员分配的存储单元中a,从而产生一个总大小为4字节的结构.
GCC正确的行为和包装不使用相同类型的两个成员发生时,或者当一个unsigned和其他signed,但类型unsigned和_Bool似乎是由海湾合作委员会被认为过于鲜明它正确地处理它们.
有人可以证实我对标准的解释,这确实是一个GCC错误吗?
我也对一种解决方法感兴趣(一些编译器开关,pragma,__attribute__......).
我正在使用gcc 4.7.0 -std=c11(尽管其他设置显示相同的行为.)
我在这里读到位字段不可移植.这是否意味着下面定义位字段的代码(从这里获取的代码)无法在某些机器上编译?
如果是这样,为什么呢?
#include <stdio.h>
#include <string.h>
/* define simple structure */
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status1;
/* define a structure with bit fields */
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status2;
int main( )
{
printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
return 0;
}
Run Code Online (Sandbox Code Playgroud) 在C结构中,可以指定另一个位长度,而不是类型的默认位长度,如下所示:
struct MyStruct{
int myVar : 1; //Size of myVar is 1 bit (so it can take values 0 or 1
int myOtherVar: 4; //Size of myOtherVar is 4 bits (so it can take values 0 to 15)
}
Run Code Online (Sandbox Code Playgroud)
这称为位字段.
我的问题是,是否也可以在C++类中执行此操作,如下所示:
class MyClass{
public:
//Some methods
private:
int m_myAttribute : 1;
int m_myOtherAttribute : 4;
}
Run Code Online (Sandbox Code Playgroud)
我在网上搜索了这个,但我找到的所有比特字段的例子都使用了结构,而不是类.
我测试了这段代码并且编译得很好,但是我想知道属性的大小是否真的是指定的大小,或者编译器是否忽略了位字段并使用了标准int大小.
bit-fields ×10
c ×6
c++ ×5
gcc ×2
unions ×2
arm ×1
c++11 ×1
c11 ×1
c99 ×1
class ×1
compiler-bug ×1
g++ ×1
optimization ×1
portability ×1
struct ×1
structure ×1
volatile ×1
warnings ×1