嵌入式c自我修养

开始记录一些在内核里看见的奇怪语法,慢慢认清,学会他们的写法

宏定义

写一个比较大小的宏定义
差劲
#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 内核第一宏

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. 这句话返回宏出来的值

动态数组 ->0长度数组的运用
0长度数组的概念

我们使用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];
};
附加: 为啥不用指针代替数组
  1. 指针和数组也有不同的地方,数组名不占空间,表示一段连续的地址
    指针是变量,是占空间的
  2. 0长度数组更加巧妙,不会对结构体造成冗余

关键字

typeof

通过使用typeof可以知道 我们的参数类型

int i ;
typeof(i) j = 20;
typeof(int *) a;
int f();
typeof(f()) k;
attribute

attribute指导编译器在编译程序时进行特定方面的优化或代码检查
下面是attribute支持填写的属性

section
aligned
packed
format
weak
alias
noinline
always_inline
……
attribute -> 变量对齐: aligned packed

举个例子 让一个变量进行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));
attribute-> 属性声明:section

使用atttribute 来声明一个 section 属性,主要用途是在程序编译时,将一个函数或变
量放到指定的段,即 section 中。

怎么使用setion呢

先看看代码分段(不是进程空间模型)

section组成
代码段( .text)函数定义、程序语句
数据段( .data)初始化的全局变量、初始化的静态局部变量
BSS段( .bss)未初始化的全局变量、未初始化的静态局部变量

这样就把本来在 .bss段的 未初始化变量 uniit_val 放在
数据段 .data中

int global_val = 8;
int uninit_val __attribute__((section(".data")));
int main(void) { 
	return 0;
 }
attribute-> 属性声明:format

大概就是支持 变参函数的检查
比如说下面的自定义了一个 打印函数 LOG 我们希望检查参数像printf一样检查

__attribute__(( format (archetype, string-index, first-to-check))) void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));
变参函数的设计和实现1.0

通过指针打印出每个变量

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; 
}
变参函数的设计和实现2.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; 
}
变参函数的设计和实现3.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;
}
变参函数的设计和实现4.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;
 }
attribute-> 属性声明:weak

基本使用方法如下

void __attribute__((weak)) func(void); 
int num __attribte__((weak);

对于变量,有效拒绝重定义等问题
对于函数,在没有这个函数的时候,编译不会报错,运行用到这个函数才会报错
可以在开发库的时候预留很多接口

attribute-> 属性声明:alias

相当于给函数重新定义了一个别名

void __f(void) 
{
	printf("__f\n");
}
 void f() __attribute__((alias("__f"))); 
 int main(void) {
 	f(); 
 	return 0;
 }
内联函数
attribute-> 属性声明:noinline & always_inline

大致就是这个函数需不需要展开
如果函数短,而且还多次使用,使用过内联函数可以加快运行速度

static inline __attribute__((noinline)) int func(); 
static inline __attribute__((always_inline)) int func();
内建函数

意思是编译器给我们提供的自带函数 比如:
用来处理变长参数列表;
用来处理程序运行异常;
程序的编译优化、性能优化;
查看函数运行中的底层信息、堆栈信息等;
C 标准库函数的内建版本。
下面列一些好用的

__builtin_return_address(LEVEL) 函数调用层级

这个函数用来返回当前函数或调用者的返回地址。函数的参数 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;
}


内建函数:__builtin_constant_p(n)

编译器内部还有一些内建函数,主要用来编译优化、性能优化,如 __builtinconstantp(n) 函数。该函数主要用来判断参数 n 在编译时是否
为常量,是常量的话,函数返回1;否则函数返回0。该函数常用于宏定义中,用于编译优化。

内建函数:__builtin_expect(exp,c)

这个函数的意义主要就是告诉编译器:参数 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;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
宋宝华嵌入 C/C++语言精华文章集锦 C/C+语言 struct 深层探索 ............................................................................2 C++中 extern "C"含义深层探索........................................................................7 C 语言高效编程的几招...............................................................................11 想成为嵌入程序员应知道的 0x10 个基本问题 .........................................................15 C 语言嵌入系统编程修炼...........................................................................22 C 语言嵌入系统编程修炼之一:背景篇 ............................................................22 C 语言嵌入系统编程修炼之二:软件架构篇 ........................................................24 C 语言嵌入系统编程修炼之三:内存操作 ..........................................................30 C 语言嵌入系统编程修炼之四:屏幕操作 ..........................................................36 C 语言嵌入系统编程修炼之五:键盘操作 ..........................................................43 C 语言嵌入系统编程修炼之六:性能优化 ..........................................................46 C/C++语言 void 及 void 指针深层探索 .................................................................50 C/C++语言可变参数表深层探索 .......................................................................54 C/C++数组名与指针区别深层探索 .....................................................................60 C/C++程序员应聘常见面试题深入剖析(1) ..............................................................62 C/C++程序员应聘常见面试题深入剖析(2) ..............................................................67 一道著名外企面试题的抽丝剥茧 ......................................................................74 C/C++结构体的一个高级特性――指定成员的位数 .......................................................78 C/C++中的近指令、远指针和巨指针 ...................................................................80 从两道经典试题谈 C/C++中联合体(union)的使用 ......................................................81 基于 ARM 的嵌入 Linux 移植真实体验 ................................................................83 基于 ARM 的嵌入 Linux 移植真实体验(1)――基本概念 ...........................................83 基于 ARM 的嵌入 Linux 移植真实体验(2)――BootLoader .........................................96 基于 ARM 的嵌入 Linux 移植真实体验(3)――操作系统 ..........................................111 基于 ARM 的嵌入 Linux 移植真实体验(4)――设备驱动 ..........................................120 基于 ARM 的嵌入 Linux 移植真实体验(5)――应用实例 ..........................................135 深入浅出 Linux 设备驱动编程 .......................................................................144 1.Linux 内核模块..............................................................................144 2.字符设备驱动程序 ...........................................................................146 3.设备驱动中的并发控制 .......................................................................151 4.设备的阻塞与非阻塞操作 .....................................................................157
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值