你有没有用宏##粘贴函数,然后用函数指针找到并执行?今天给大家讲讲宏的使用,大家经常用到,但一般只是简单定义一个符号常量,类似#defineWHEEL_SCALE_MM0.53f,#defineLOG_I(tag,text_fmt,...)log_i(tag,text_fmt,##__VA_ARGS__),但是除此之外,宏还有一个##paste函数,可以和#define的常量表达式一起使用,做一个宏定义指针函数的列表,然后查询和执行函数。话不多说,开始今天的分享,首先我们做一个大概的描述,然后分享“##”粘贴的妙用。1、#define的常规操作#define预处理器指令与其他预处理器指令一样,以#作为一行的开始。ANSI和更高版本的标准都允许在#符号之前使用空格或制表符,并且还允许在#和指令的其余部分之间使用空格。但是旧版本的C要求指令从最左边的行开始,#和指令的其余部分之间不能有空格。指令可以出现在源文件的任何地方,它的定义从指令出现的地方到文件末尾都是有效的。我们大量使用#define指令来定义清单常量(也称为符号常量)。预处理器指令从#运行到后面的第一个换行符。也就是说,指令的长度限于一行。但是,编译器在预处理开始之前将多个物理行视为一个逻辑行。一般我们会用#define来表示常量,或者做一个简单的宏替换函数#defineRX_BUF_SIZE30#defineMBEDTLS_DES_C/*数据加密*/#defineExitIsrEncoder_IsrvoidEncoder_Isr(void){g.dir_count+=(g.dir==1)?1:-1;}每行#define(逻辑行)由3部分组成。第1部分是#define指令本身。第2部分是选定的缩写,也称为宏。有些宏表示值(就像这个例子中的那样),这些宏被称为类对象宏。C语言也有类似函数的宏,稍后讨论。宏名中不允许有空格,必须遵循C变量的命名规则:只能使用字符、数字和下划线(_)字符,首字符不能为数字。第3部分(命令行的其余部分)称为替换列表或替换主体。一旦预处理器在程序中找到宏的实例,它就会用替换体替换宏。从宏到最终替换文本的过程称为宏展开。请注意,可以在#define行上使用标准C注释。如前所述,每条评论都用一个空格代替。此外,我们将使用更多的可变宏参数来实现此功能,方法是将宏参数列表中的最后一个参数写为省略号(即3个点...)。因此,可以在替换部分中使用预定义宏__VA_ARGS__来指示省略号代表什么。#definePR(...)printf(__VA_ARGS__)假设稍后调用宏:PR("Howdy");PR("重量=%d,运费=$%.2f\n",wt,sp);1个调用,__VA_ARGS__扩展为1个参数:“你好”。对于第二次调用,__VA_ARGS__扩展为3个参数:"weight=%d,shipping=$%.2f\n",wt,sp。因此,展开后的代码为:printf("Howdy");printf("重量=%d,运费=$%.2f\n",wt,sp);二、#define配合##使用很多人应该都知道“##”的用法,它叫做预处理胶水,类似于#运算符,##运算符可以用在类函数的替换部分宏。而且##也可以用在对象宏的替换部分。##运算符可以将两个标记合并为一个标记。#definedef_u32_array(__name,__size)uint32_tarray_##__name[__size];其实我们可以这样使用:def_u32_array(sample_buffer,64)宏展开后的效果为:uint32_tarray_sample_buffer[64];类似于初始化一个数组,我们也可以Paste组成一个函数下面是Linux内核中的源码:__pcpu_size_call_return宏,选择##paste要使用的raw_cpu_read_x函数。#define__pcpu_size_call_return(stem,variable)\({\typeof(variable)pscr_ret__;\__verify_pcpu_ptr(&(variable));\switch(sizeof(variable)){\case1:pscr_ret__=stem##1(variable);break;\case2:pscr_ret__=stem##2(变量);break;\case4:pscr_ret__=stem##4(变量);break;\case8:pscr_ret__=stem##8(变量);break;\default:\__bad_size_call_parameter();break;\}\pscr_ret__;\})#defineraw_cpu_read_1(pcp)raw_cpu_generic_read(pcp)#defineraw_cpu_generic_read(pcp)\({\*raw_cpu_ptr(&(pcp));\})这部分是更高层次的宏定义,再次将##pasted函数定义为宏函数#defineraw_cpu_read(pcp)__pcpu_size_call_return(raw_cpu_read_,pcp)#define__this_cpu_read(pcp)\({\__this_cpu_preempt_check("read");\raw_cpu_read(pcp);\})最后执行__this_cpu_read(current_kprobe);int__kprobesarc_kprobe_handler(unsignedlongaddr,structpt_regs*regs){structkprobe*pp=__this_cpu_read(current_kprobe);p=get_kprobe((unsignedlong*)addr);...省略多行代码if(p->break_handler&&p->break_handler(p,regs)){setup_singlestep(p,regs);kcb->kprobe_status=KPROBE_HIT_SS;return1;}在C++中,我们也可以做一个指针列表。对应各个函数名后,再次调用定义好的宏参数,实现指针调用std::make_pair(mode_name,state_name);\automapEntry=modeMap->find(pair);\if(mapEntry==modeMap->end())\returnullptr;\returnmapEntry->second;\}\boolMode::Func(状态*状态){\autostate_id=getStateId();\autop_function=Func##Map(getId(),state_id);\if(p_function)\returnp_function(this,state);\returnfalse;\}AddFunc(IsExit);intmain(){IsExit(p.get());}这也是Linux内核中的代码,用来打印不同状态的打印信息。如果你想快速掌握这些使用方法,我建议你看看Linux内核的源代码。本文转载自微信公众号“羽林君”,可通过以下二维码关注。转载本文请联系羽林君公众号。
