开始记录一些在内核里看见的奇怪语法,慢慢认清,学会他们的写法
#define MAX(x,y) x > y ? x : y
会因为传入的宏带有其他符号(考虑优先级)而改变我们的想法
比如 : printf(“max=%d”,MAX(1!=1,1!=2));
#define MAX(x,y) (x) > (y) ? (x) : (y)
括号外的符号也会有类似的优先级
比如 : printf(“max=%d”,MAX(1!=1,1!=2));
#define MAX(x,y) ((x) > (y) ? (x) : (y))
会因为传入的值产生变化
比如 : printf(“max=%d”,MAX(i++,j++));
#define MAX(x,y)({ \
int _x = x; \
int _y = y; \
_x > _y ? _x : _y; \
})
没什么大问题了,使用局部变量来存储值,但是有时候因为前面加了类型,所以可以更好
传入一个type类型,可以让这个宏通用
#define MAX(type,x,y)({ \
type _x = x; \
type _y = y; \
_x > _y ? _x : _y; \
})
1.让typeof直接获取参数里面的类型
2.(void) (&_x == &_y);
a. 编译器会因为类型不同给出警告
b.两个值比较后,结果没有用到,可以用这个消除warning
#define max(x, y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y);\
_x > _y ? _x : _y; })
详细解释 (&_x == &_y)
在这里就是判断两个值是否相等,
但是如果两个值的类型不一样,那么判断的时候,使用两个值的地址能让其编译通过,但编译器会警告有问题
container_of 得到一个结构体的变量,可以得出这个结构体
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
1 用 offsetof 来确定这个变量在结构体里面的偏移值
原理 : 假设结构体的地址是0,那么这个成员变量的偏移值就是当前地址
2 container_of 用之前的偏移值 - 现在结构体这个成员的地址 = 当前结构体的地址
a. 开始构造了一个 __mptr 来防止变量的改变
b. 这句话返回宏出来的值
我们使用gcc 编译器的时候,就能使用0长度数组
比如说下面这个结构体,他的sizeof只有4,因为0长度数组不占内存
struct buffer{
int len;
int a[0];
};
有时候可以对这个结构体 malloc的时候多增加内存,使用这个0长度数组来控制
分配内存的数值
struct buffer{
int len;
int a[0];
};
int main(void) {
struct buffer *buf;
buf = (struct buffer *)malloc \
(sizeof(struct buffer)+ 20);
buf->len = 20;
strcpy(buf->a, "hello wanglitao!\n");
puts(buf->a);
free(buf);
return 0;
}
比如说一个usb的驱动,usb结构体有着usb各种各样的描述,但结构体最后要放数据包
数据包的长度由于很多种usb设备所以长度不固定,使用一个0长度数组,描述特定的usb设备再分配buffer
struct urb {
struct kref kref; void *hcpriv;
atomic_t use_count; atomic_t reject;
int unlinked; struct list_head urb_list;
struct list_head anchor_list; struct usb_anchor *anchor;
struct usb_device *dev; struct usb_host_endpoint *ep;
unsigned int pipe; unsigned int stream_id; int status;
unsigned int transfer_flags; void *transfer_buffer;
dma_addr_t transfer_dma; struct scatterlist *sg;
int num_mapped_sgs; int num_sgs; u32 transfer_buffer_length;
u32 actual_length; unsigned char *setup_packet;
dma_addr_t setup_dma; int start_frame; int number_of_packets;
int interval; int error_count; void *context; usb_complete_t complete;
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
通过使用typeof可以知道 我们的参数类型
int i ;
typeof(i) j = 20;
typeof(int *) a;
int f();
typeof(f()) k;
attribute指导编译器在编译程序时进行特定方面的优化或代码检查
下面是attribute支持填写的属性
section
aligned
packed
format
weak
alias
noinline
always_inline
……
举个例子 让一个变量进行4字节对齐 有下面的一些写法
char c2 __attribute__((packed,aligned(4)));
char c2 __attribute__((packed,aligned(4))) = 4;
__attribute__((packed,aligned(4))) char c2 = 4;
int a __attribute__((aligned(8));
使用atttribute 来声明一个 section 属性,主要用途是在程序编译时,将一个函数或变
量放到指定的段,即 section 中。
先看看代码分段(不是进程空间模型)
section | 组成 |
---|---|
代码段( .text) | 函数定义、程序语句 |
数据段( .data) | 初始化的全局变量、初始化的静态局部变量 |
BSS段( .bss) | 未初始化的全局变量、未初始化的静态局部变量 |
这样就把本来在 .bss段的 未初始化变量 uniit_val 放在
数据段 .data中
int global_val = 8;
int uninit_val __attribute__((section(".data")));
int main(void) {
return 0;
}
大概就是支持 变参函数的检查
比如说下面的自定义了一个 打印函数 LOG 我们希望检查参数像printf一样检查
__attribute__(( format (archetype, string-index, first-to-check))) void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));
通过指针打印出每个变量
void print_num(int count, ...)
{
int *args;
args = &count + 1;
for( int i = 0; i < count; i++)
{
printf("*args: %d\n", *args);
args++;
}
}
int main(void)
{
print_num(5,1,2,3,4,5);
return 0;
}
这次通过char* 来实现这个功能,就可以兼容更多的参数类型
void print_num(int count, ...)
{
char *args;
args = (char*)&count + 4;
for( int i = 0; i < count; i++)
{
printf("*args: %d\n", *(int *)args);
args +=4;
}
}
int main(void)
{
print_num(5,1,2,3,4,5);
return 0;
}
对于变参函数,编译器或计算机系统一般会提供一些宏给程序员使用
用这些宏上面的函数就能简化
va_list:定义在编译器头文件中 typedef char* va_list; 。
va_start(args,fmt):根据参数 fmt 的地址,获取 fmt 后面参数的地址,并保存在 args 指针变量中。
va_end(args):释放 args 指针,将其赋值为 NULL。有了这些宏,我们的工作就简化了很多。我们就不用撸起袖子,自己解析了。
void print_num3(int count,...)
{
va_list args;
va_start(args,count);
for(int i = 0; i < count; i++)
{
printf("*args: %d\n", *(int *)args);
args += 4;
}
va_end(args);
}
int main(void)
{
print_num3(5,1,2,3,4,5);
return 0;
}
在 V3.0 版本中,我们使用编译器提供的三个宏,省去了解析参数的麻烦。但打印的时候,我们还必须自己实现。在 V4.0 版本中,我们继续改进,使用 vprintf 函数实现我们的打印功能。vprintf 函数的声明在 stdio.h 头文件中。
CRTIMP int __cdecl __MINGW_NOTHROW \
vprintf (const char*, __VALIST);
vprintf 函数有2个参数,一个是格式字符串指针,一个是变参列表。在下面的程序里,我们可以将,使用 va_start 解析后的变参列表,直接传递给 vprintf 函数,实现打印功能。
void my_printf(char *fmt,...)
{
va_list args;
va_start(args,fmt);
vprintf(fmt,args);
va_end(args);
}
int main(void)
{
int num = 0;
my_printf("I am litao, I have %d car\n", num);
return 0;
}
实现自己的日志打印函数
有这个东西,我们用宏就可以整体调整要打印,debug等快速打开关闭
#define DEBUG //打印开关
void __attribute__((format(printf,1,2))) LOG(char *fmt,...)
{
#ifdef DEBUG va_list args;
va_start(args,fmt);
vprintf(fmt,args);
va_end(args);
#endif
}
int main(void)
{
int num = 0;
LOG("I am litao, I have %d car\n", num);
return 0;
}
基本使用方法如下
void __attribute__((weak)) func(void);
int num __attribte__((weak);
对于变量,有效拒绝重定义等问题
对于函数,在没有这个函数的时候,编译不会报错,运行用到这个函数才会报错
可以在开发库的时候预留很多接口
相当于给函数重新定义了一个别名
void __f(void)
{
printf("__f\n");
}
void f() __attribute__((alias("__f")));
int main(void) {
f();
return 0;
}
大致就是这个函数需不需要展开
如果函数短,而且还多次使用,使用过内联函数可以加快运行速度
static inline __attribute__((noinline)) int func();
static inline __attribute__((always_inline)) int func();
意思是编译器给我们提供的自带函数 比如:
用来处理变长参数列表;
用来处理程序运行异常;
程序的编译优化、性能优化;
查看函数运行中的底层信息、堆栈信息等;
C 标准库函数的内建版本。
下面列一些好用的
这个函数用来返回当前函数或调用者的返回地址。函数的参数 LEVEl 表示函数调用链中的不同层次的函数
/*0:返回当前函数的返回地址;
1:返回当前函数调用者的返回地址;
2:返回当前函数调用者的调用者的返回地址;
……
*/
void f(void) {
int *p;
p = __builtin_return_address(0);
printf("f return address: %p\n", p);
p = __builtin_return_address(1);;
printf("func return address: %p\n", p);
p = __builtin_return_address(2);;
printf("main return address: %p\n", p);
printf("\n");
}
void func(void) {
int *p;
p = __builtin_return_address(0);
printf("func return address: %p\n", p);
p = __builtin_return_address(1);;
printf("main return address: %p\n", p);
printf("\n");
f();
}
int main(void) {
int *p;
p = __builtin_return_address(0);
printf("main return address: %p\n", p);
printf("\n");
func();
printf("goodbye!\n");
return 0;
}
编译器内部还有一些内建函数,主要用来编译优化、性能优化,如 __builtinconstantp(n) 函数。该函数主要用来判断参数 n 在编译时是否
为常量,是常量的话,函数返回1;否则函数返回0。该函数常用于宏定义中,用于编译优化。
这个函数的意义主要就是告诉编译器:参数 exp 的值为 c 的可能性很大。然后编译器可能就会根据这个提示信息,做一些分支预测上的代码优化。
这个函数有两个参数,返回值就是其中一个参数,仍是 exp
参数 c 跟这个函数的返回值无关,无论 c 为何值,函数的返回值都是 exp。
int main(void) {
int a;
a = __builtin_expect(3, 1);
printf("a = %d\n", a);
a = __builtin_expect(3, 10);
printf("a = %d\n", a);
a = __builtin_expect(3, 100);
printf("a = %d\n", a);
return 0;
}