1. 指针
指针是嵌入式开发用得最多的,直接操作硬件地址全靠指针。
重点理解:
- 指针就是地址,指针变量存储的是一个内存地址
- 指针和数组的关系,
arr[0]和*arr是什么关系 - 二级指针,指针的指针
- 函数指针 —— 这个在驱动里用得非常多
举个例子:操作硬件寄存器,就是这么干的:
// GPIOA端口的基地址是 0x40020000
#define GPIOA_BASE 0x40020000
// 数据寄存器偏移是 0x14
#define GPIOA_DATA (GPIOA_BASE + 0x14)
// 通过指针读写这个寄存器
*((volatile unsigned int *)GPIOA_DATA) = 0xFF;
看到了吗?就是把地址强制转换成指针,然后解引用读写。不理解指针,这段代码你就看不懂。
2. 结构体
结构体用来干嘛?表示硬件寄存器啊!
一个外设往往有很多个寄存器,它们的地址是连续的,我们可以定义一个结构体来对应:
typedef struct {
unsigned int MODER; // 模式寄存器,偏移 0x00
unsigned int OTYPER; // 输出类型寄存器,偏移 0x04
unsigned int OSPEEDR; // 速度寄存器,偏移 0x08
unsigned int PUPDR; // 上拉下拉寄存器,偏移 0x0C
// ... 其他寄存器
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
// 然后就可以这么用了
GPIOA->MODER |= (1 << 0); // 设置PA0为输出模式
结构体这里要注意内存对齐,不对齐的话寄存器地址就错了,程序肯定跑飞。
3. 内存管理
malloc和free,什么时候用,为什么要避免内存泄漏- 栈空间和堆空间的区别 —— 嵌入式栈空间一般很小,别定义太大的局部变量
- 野指针问题 —— 用完指针记得置空,释放了别再访问
4. 函数指针
驱动框架里大量使用函数指针,把你的操作函数注册到框架里:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// ... 很多函数指针
};
这是Linux内核里的字符设备操作函数集合,你要实现read、write这些函数,然后把地址赋值给函数指针,内核就会调用你的函数。不理解函数指针根本看不懂驱动代码。
C语言学到什么程度够了
不用你去写复杂的算法,也不需要精通C++,把上面这些基础概念吃透,能看懂别人的代码,能写出简单的模块就够了。边做项目边提高完全没问题。
C语言推荐学习资料
- 书籍:《C Primer Plus》适合入门,《C和指针》进阶,《深入理解计算机系统》讲指针和内存
- 练习:去LeetCode刷一些简单题,重点练练数组、链表操作