GNU C编译器扩展语法精讲(六)
github Demo:click here
指定初始化
1 | 1. 数组初始化 |
语句表达式
- 目的:构造复杂宏
- 结构:( { 表达式1; 表达式2; 表达式3 } ) 语句表达式用()小括号括起来,代码块用 { } 大括号括起来,语句表达式的值等于最后一个表达式的值
示例
求和
1
2
3
4
5
6sum = ( {
int s = 0;
for(int i=0; i<10; i++>)
s = s +i;
s;
} )定义一个宏,求两个数的最大值?
1
2
3
4
5
6
7
// 第六行的作用:不同类型的指针比较会给出警告,加void消除没有用到比较结果带来的警告
container_of宏
作用:根据结构体某一成员地址,获取这个结构体的首地址
场景:传给某个函数的参数是某个结构体的成员变量,在这个函数中还可能用到这个结构体其它变量
基础:常量指针的值即为常量本身的值。0强转为结构体指针,结构体的首地址为0,每个成员变量都是首地址的相对偏移
实现
1
2
3
4
5
const typeof(((type*)0)->member)* _ptr = (ptr); \
(type*) ((char*)_ptr - offset(type, member)); \
})
零长数组
- 定义:长度为0的数组
- 优点:不占用内存存储空间
- 其它:C99支持变长数组,即长度编译时期是不能确定,在运行时才可以确定
- 应用:变长结构体
- 数组与指针区别:数组名用来表征一块连续的内存地址空间,仅仅是符号不分配空间;指针是一个变量,编译器需要单独给它分配一个内存空间,用来存放他指向的变量的地址。
1 | struct buffer { |
属性声明
定义
__attribute__
- 作用:指导编译器在编译程序的时候进行特定方面优化或代码检查
- 格式:attribute(( )),后面需要写两对小括号,多个属性之间逗号隔开
- 支持的属性声明:section、aligned、packed、format、weak、alias、noinline、always_inilne
section
作用:将一个函数或变量放到指定的段,即放到指定的section
使用:attribute(( section(“.data”) ))
可选项:
.text:函数定义、程序语句
.data:初始化的全局变量、初始化的静态局部变量
.bss:未初始化的全局变量、未初始化的静态局部变量
aligned
- 作用:地址对齐,在给变量分配存储空间的时候,需要按照制定的地址对齐方式给变量分配地址
- 注意:地址对齐的字节数必须是2的幂次方
- 使用:attribute(( (aligned(4) ))
- 对齐的好处:简化CPU与RAM之间的接口和硬件设计。cpu访存硬件设计可能只支持4字节或4字节整数倍对齐的地址访问,对齐后cpu可以一次读完
- 注意:这个属性声明只是建议编译器按照这种方式对齐,当超过编译器允许的最大值只能按照最大对齐字节数进行地址分配
packed
- 作用:减少地址对齐
- 使用: attribute(( packed ))
- 通常aligned与packed一起使用,这样既避免了结构体各个成员变量地址对齐产生的内存空洞又指定了整个结构体对齐方式:attribute(( packed, aligned(8) ))
format
- 作用:指定变参函数的参数格式检查
1 | void LOG(int num, char* fmt, ...) __attribute__((format(printf,2,3)))) |
weak
- 作用:可以将一个强符号转换为弱符号
- 使用:void _attribute(( weak )) func(void);
- 好处:当一个函数被声明为一个弱符号之后,链接阶段找不到函数定义不会报错会将这个弱符号设置为0或者特殊值。当程序运行时,调用这个这个函数跳转到零地址或特殊地址才会产生一个内存错误
alias
- 作用:给函数定义一个别名
- 使用:void f() attribute((alias(“_f “)));
- 通常在linux内核中weak和alias一起使用,随着版本升级可以封装旧名字起一个新名字
内联函数
特征:用inline修饰的函数被称为内联函数,通常用static 或 extern 修饰
内联函数特征:
- 函数体积小
- 函数体内无指针赋值、递归、循环等语句
- 调用频繁
与宏相比,内联函数的优点:
- 参数类型检查,本质仍然是函数
- 便于调试。可以单步、断点调试
- 有返回值(宏也可以,语句表达式结构)
- 接口封装
使用:
1
2
3
4
5// 显式告诉编译器不要展开
static inline __attribute((noinline)) int func();
// 显式告诉编译器要内联展开
static inline __attribute((always_inline)) int func();
// 否则编译器的行为不固定,考虑诸多因素决定是否展开
内建函数
- 含义:编译器内部实现的函数
- 特征:通常以 __builtin 开头
- 说明:没有文档,变动频繁,不建议应用程序工程师使用
1 | // __builtin_return_address(LEVEL) 返回当前函数或者调用者的返回地址 |
可变参数宏
- 用 … 表示变参列表,变参列表由不确定的参数组成,用 VA_ARGS 标识符表示变参函数列表
- C99规定,可以使用 args… 表示变参列表,用 args 代表变参列表即可
- 宏连接符 ## 的作用是连接两个字符串
- 在定义宏的时候通常使用 do { } while(0);的结构,这样是为了防止宏在选择、条等分支语句中展开后,产生宏歧义
两种方法实现自定义等级宏打印:click here
Uboot镜像自复制
- 作用:加载linux内核镜像到内存,给内核传递启动参数,然后引导linux操作系统启动
- 过程:通过定义两个零长度数组,分别表示要复制自身镜像的起始地址和结束地址,并放在特定的section上,而这两个section分别放在代码段前和数据段后。数组名本身就代表一个地址,通过arm ldr伪指令直接获取要复制的镜像首地址保存在某个寄存器中,从flash复制自身镜像到内存中,然后重定位,最终跳转到内存中执行。(自举)
C语言面向对象编程思想(八)
代码复用与分层
复用:
- 函数级复用
- 库级复用
- 框架级复用
- 操作系统级复用
分层:
- 应用层
- Framework层
- 库
- Linux内核
面向对象主要特征
继承、多态、纯虚函数特征(抽象类)
模块化(九)
模块化的思想内核是分而治之,重点在于抽象的对象之间的关联,而不是内容
面向对象的编程思想主要是为了代码复用,重点在于内容的实现