Fork me on GitHub
0%

《嵌入式C语言自我修养》笔记

img

GNU C编译器扩展语法精讲(六)

github Demo:click here

指定初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1. 数组初始化
int array[100] = { [10] = 1, [20] = 3 };

# ... 前后都需要空格,并且该特性也可以用在switch-case语句中
int array[100] = { [10 ... 20] = 1, [40 ... 50] = 2 };
switch(i) {
case 1:
break;
case 2 ... 8:
break;
}


2. 结构体成员初始化 (通过结构域名)
例如linux内核:
struct const file_opt codec_opt = {
.open = codec_open,
.read = codec_read,
.write = codec_write,
}
好处:灵活,代码易于维护

语句表达式

  • 目的:构造复杂宏
  • 结构:( { 表达式1; 表达式2; 表达式3 } ) 语句表达式用()小括号括起来,代码块用 { } 大括号括起来,语句表达式的值等于最后一个表达式的值

示例

  1. 求和

    1
    2
    3
    4
    5
    6
    sum = ( {
    int s = 0;
    for(int i=0; i<10; i++>)
    s = s +i;
    s;
    } )
  2. 定义一个宏,求两个数的最大值?

    1
    2
    3
    4
    5
    6
    7
    #define my_max(x,y)   ({    \
    typeof(x) _x = (x); \
    typeof(y) _y = (y); \
    (void) (&_x == &_y); \
    _x > _y ? _x : _y; \
    })
    // 第六行的作用:不同类型的指针比较会给出警告,加void消除没有用到比较结果带来的警告

container_of宏

  • 作用:根据结构体某一成员地址,获取这个结构体的首地址

  • 场景:传给某个函数的参数是某个结构体的成员变量,在这个函数中还可能用到这个结构体其它变量

  • 基础:常量指针的值即为常量本身的值。0强转为结构体指针,结构体的首地址为0,每个成员变量都是首地址的相对偏移

  • 实现

    1
    2
    3
    4
    5
    #define offset( TYPE , MEMBER ) ((size_t) & (((TYPE*)0)->MEMBER))
    #define container_of(ptr, type, member) ({ \
    const typeof(((type*)0)->member)* _ptr = (ptr); \
    (type*) ((char*)_ptr - offset(type, member)); \
    })

零长数组

  • 定义:长度为0的数组
  • 优点:不占用内存存储空间
  • 其它:C99支持变长数组,即长度编译时期是不能确定,在运行时才可以确定
  • 应用:变长结构体
  • 数组与指针区别:数组名用来表征一块连续的内存地址空间,仅仅是符号不分配空间;指针是一个变量,编译器需要单独给它分配一个内存空间,用来存放他指向的变量的地址。
1
2
3
4
5
6
7
struct buffer {
int len;
int ptr[0]; // 不占大小
};
struct buffer* buf;
buf = (struct buffer*) malloc(sizeof(struct buffer) + 100);
buf->len = 100;

属性声明

定义

__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
2
void LOG(int num, char* fmt, ...) __attribute__((format(printf,2,3))))
// LOG函数的参数,个数字符串的位置在所有参数列表的索引是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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//  __builtin_return_address(LEVEL)    返回当前函数或者调用者的返回地址
// LEVEL 0: 获取当前函数返回地址
// LEVEL 1: 获取上一级函数返回地址
// LEVEL 2: 获取上两级函数返回地址


// __builtin_frame_address(LEVEL) 查看函数的栈帧地址
// LEVEL 0: 获取当前函数栈帧地址
// LEVEL 1: 获取上一级函数栈帧地址

// __builtin_constant_p(n) 判断参数n在编译时是否是常量,是常量返回1,否则0
// 内核中判断宏的参数是否是常数,则有更优化的实现方法

// __builtin_expect(exp,c)
// 告诉编译器参数exp的值为c的可能性很大,用于分支预测,参数c与返回值无关,返回值一定是exp

# define likely(x) __builtin_expect(!!(x),1))
# define unlikely(x) __builtin_expect(!!(x),0)
// 告诉编译器某一个分支发生的概率很高,自己做优化
// 两次取非是为了将参数x转换为bool类型,与1/0比较

可变参数宏

  • 用 … 表示变参列表,变参列表由不确定的参数组成,用 VA_ARGS 标识符表示变参函数列表
  • C99规定,可以使用 args… 表示变参列表,用 args 代表变参列表即可
  • 宏连接符 ## 的作用是连接两个字符串
  • 在定义宏的时候通常使用 do { } while(0);的结构,这样是为了防止宏在选择、条等分支语句中展开后,产生宏歧义

两种方法实现自定义等级宏打印:click here

Uboot镜像自复制

  • 作用:加载linux内核镜像到内存,给内核传递启动参数,然后引导linux操作系统启动
  • 过程:通过定义两个零长度数组,分别表示要复制自身镜像的起始地址和结束地址,并放在特定的section上,而这两个section分别放在代码段前和数据段后。数组名本身就代表一个地址,通过arm ldr伪指令直接获取要复制的镜像首地址保存在某个寄存器中,从flash复制自身镜像到内存中,然后重定位,最终跳转到内存中执行。(自举)

C语言面向对象编程思想(八)

代码复用与分层

复用:

  • 函数级复用
  • 库级复用
  • 框架级复用
  • 操作系统级复用

分层:

  • 应用层
  • Framework层
  • Linux内核

面向对象主要特征

继承、多态、纯虚函数特征(抽象类)

模块化(九)

模块化的思想内核是分而治之,重点在于抽象的对象之间的关联,而不是内容

面向对象的编程思想主要是为了代码复用,重点在于内容的实现