一. 宏定义
在C语言编程规范中,使用宏时一般不允许参数变化,但是万一遇到这个情况怎么办呢
1.1 定义局部变量
通过定义局部变量来存储宏参数的值,可以避免参数被多次求值的问题。这在处理有副作用的操作(如自增、自减)时尤为重要。
#define MAX(x)({
int _x = (x);
int _y = y;
_x > _y ? _x : y;
})
_x
,__y 是一个局部变量,它存储了 x
,y的值,从而确保 x,y只被求值一次。
1.2 引入一个type类型参数,来适配不同的数据类型。
#define MAX(type,x,y)({
type _x = (x);
type _y = y;
_x > _y ? _x : y;
})
1.3 去掉参数类型
·(void) (&_x == &_y);–作用
·一是用来给用户提示一个警告,对于不同类型的指针比较,编译器会给出一个警告,提示两种数据的类型不同
·二是两个数进行运算,运算的结果却没有用,有些编译器可能会给出一个warning,加一个(void)就可以消除这个警告
#define MAX(x,y)({
typeof(x) _x = (x);
typeof(y) _y = (y);
(void) (&_x == &_y);
_x > _y ? _x : _y;
})
二. 扩展关键字
typeof: 可以获取一个变量或表达式的类型
container_of:可以通过结构体类型和结构体内某一成员地址获得这个结构体的首地址
attribute:指导编译器在编译程序时进行特定方面的优化或代码检查。(attribute_就是和编译器说悄悄话)
· 支持十几种属性声明
·· Section,aligned,packed,format,weak,alias,noinline,always,lnline
三. 零长度数组
长度为0的数组
不占用内存存储空间
零长度数组单独使用的机会很少,常常作为结构体的一个成员,构成一个变长结构体
可以通过结构体中的零长度数组去访问内存,大量存在于linux内核的驱动代码中,比如网卡驱动中,套接字缓冲区的实现
四. U-boot镜像自复制分析
U-boot的用途主要是加载Linux内核镜像到内存,给内核传递启动参数,然后引导Linux操作系统启动
U-boot一般存储在NORFlash或NANDFlash,启动过程需要从Flash存储介质上加载自身代码到内存,然后进行重定位跳到内存RAM中执行
那么U-boot是如何识别自身代码的?是如何知道从哪里开始复制代码的?是如何知道复制到哪里停止的?
五. 变参函数
#include <stdio.h>
#include <stdarg.h>
// 可变参数函数,计算传入参数的平均值
double average(int num, ...)
{
va_list args; // 定义一个 va_list 类型的变量
double sum = 0.0;
va_start(args, num); // 初始化 args,指向第一个可变参数
// 遍历所有参数
for (int i = 0; i < num; i++) {
sum += va_arg(args, int); // 依次获取每个参数(类型为 int)
}
va_end(args); // 结束可变参数的处理
return sum / num; // 返回平均值
}
int main()
{
printf("Average of 3, 4, 5 = %.2f\n", average(3, 3, 4, 5));
printf("Average of 2, 5, 8, 10 = %.2f\n", average(4, 2, 5, 8, 10));
return 0;
}
六. 强符号,弱符号
强符号:通常由函数或初始化的全局变量(有初始值)定义,链接器在处理强符号时遵循严格的规则。
七. 宏参数的do-while语句
使用 do { … } while(0) 来包裹宏可以确保:
宏在任何上下文中都能安全使用,特别是 if-else 等控制结构中。
宏被当作单个语句来处理,即使宏内部有多行代码,也不会影响控制流结构。