85、【OS】【Nuttx】【番外】gcc 关键字:位域(上)
【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
接之前 blog
【OS】【Nuttx】【启动】栈溢出保护:asm 关键字(上)
【OS】【Nuttx】【启动】栈溢出保护:asm 关键字(下)
分析了 asm, __asm__ 和 __asm 等关键字的概念和用法,下面最后再补充下 gcc 相关的关键字细节
gcc 关键字
上篇 blog 【OS】【Nuttx】【启动】栈溢出保护:asm 关键字(下) 提到了关键字类型 c_common_resword
这里涉及到了位域的知识点,下面来详细分析下
位域
在 C 语言中,使用位域是一种节省内存空间的技术,比较适合结构体中某些字段只需要少量比特位的情况,比如下面这里,rid : 16 表示 rid 字段占用了 16 个 bit 位,disable : 32 表示 disable 字段占用了 32 个 bit 位
下面来分析下使用位域这种方法的好处:
- 节省内存:如果一个字段只需要几个 bit 就能表示(比如状态标志、枚举值等),使用完整的 int 类型会浪费很多内存,而使用位域可以让多个字段共享同一个整数类型的存储空间,比如这里的 rid,如果用完整的 int 类型,4 个字节最大值 2147483647,足足有 21 亿个保留关键字 rid,很明显保留关键字用不了这么多(现在连 1000 个都没有),所以使用位域是比较好的选择
有好处就会有坏处,两个比较明显的:
- 不能取地址:位域成员不能使用 & 运算符取地址
- 性能代价:访问位域字段可能比普通字段慢,因为需要进行掩码和移位操作
所以总结一下,如果在意内存使用效率或需要精确控制数据布局(比如编译器,操作系统,嵌入式等场景)的底层基础设施,可以使用位域;否则,在一般应用层编程中可以不用它(因为内存很充足,随便用)
另外要补充的是,使用位域并不会节省下结构体对齐的内存,只是将原有的数据类型给缩小了,而结构体对齐要填充的内存还是会存在的,比如下面这个例子(其实就是 gcc 源码的结构体)
// main.c
#include <stdio.h>enum RidType
{RID_TEST_1 = 0U,RID_TEST_2,
};struct ResWordsType {const const char *const word;enum RidType rid : 16;const unsigned int disable : 32;
};int main(void) {printf("%ld\n", sizeof(struct ResWordsType));return 0;
}
在 x86_64 环境上,指针 word 占 8 个字节,枚举类型 rid 使用位域占 2 个字节,disable 占 4 个字节,所以理论上有效内容总共是 8 + 2 + 4 = 14 字节
在 x86_64 环境上编译,终端输入命令并运行,可以看到实际这个结构体占了 16 个字节
这里面就涉及到结构体对齐的规则:整个结构体的大小必须是其最大成员对齐要求的倍数,这里结构体 ResWordsType 里最大的成员是 word 指针,占 8 个字节,那么结构体就必须按照 8 字节进行对齐,所以有效内容 14 字节还需向上对齐 2 个字节,到 16 字节
可以看到,位域并不能把结构体对齐要填充的字节省下来,那么,如果这里不用位域呢?还是同样的代码,只不过把位域省掉了
// main.c
#include <stdio.h>enum RidType
{RID_TEST_1 = 0U,RID_TEST_2,
};struct ResWordsType {const const char *const word;enum RidType rid;const unsigned int disable;
};int main(void) {printf("%ld\n", sizeof(struct ResWordsType));return 0;
}
在 x86_64 环境上,word 指针占 8 字节,枚举成员 rid 占 4 字节,disable 成员占 4 字节,理论上刚好 16 字节,都不用填充字节了
终端输入编译命令,运行,可以看到确实也是 16 字节
那么问题来了,刚才说了用位域可以节省内存,但现在用位域和不用位域,占的空间大小一样,那用位域还有什么意义呢?
这个问题,下篇 blog 再解答