我们一般在看内核源码的时候经常看到如下代码,这就是宏函数
#define c67x00_sie_config(config, n) (((config)>>(4*(n)))&0x3)
#define cdc_ncm_comm_intf_is_mbim(x) ((x)->desc.bInterfaceSubClass == USB_CDC_SUBCLASS_MBIM && \
(x)->desc.bInterfaceProtocol == USB_CDC_PROTO_NONE)
什么是宏?
根据维基百科定义,宏就是 Macro 是一种批处理的称谓。
在计算机里的宏就是一种抽象,他根据一系列预定义规则进行替换的文本模式,C语言的宏预处理器只能完成简单的文本搜索和替换,但是这依然能够提高程序的简捷性。而C语言中宏定义也有很多指令,我们这里只讨论#define指令编程。
示例代码
这里我们定义了一个宏函数 log,对printf进行了封装
#include <stdio.h>
#define log(x, format) printf(#x "=%" #format "\n", x)
int main(void)
{
char *name = "xing xing xing";
log(name, s);
log(name, x);
return 0;
}
使用 gcc -E -C预处理后的文件
我们可以看到在预处理阶段,宏被替换为我们封装的printf函数 并完成字符转换
# 5 "./main.c"
int main(void)
{
char *name = "xing xing xing";
printf("name" "=%" "s" "\n", name);
printf("name" "=%" "x" "\n", name);
return 0;
}
使用 gcc -s 查看生成的汇编代码
我们看宏定义已经被转化为了 字符常量
.file "main.c"
.text
.section .rodata
.LC0:
.string "xing xing xing"
.LC1:
.string "name=%s\n"
.LC2:
.string "name=%x\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
leaq .LC0(%rip), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movq -8(%rbp), %rax
movq %rax, %rsi
leaq .LC2(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 13.2.1 20230801"
.section .note.GNU-stack,"",@progbits
宏定义虽然看着简单,但是它的灵活性确实毋庸置疑的,用得好可以使程序可读性和编程效率大幅提升,同时可维护性也会拔高一个层次。
宏的作用域
我们定义了如下代码
#define A 10
int main(void)
{
int a1 = A;
int b1 = B;
#define B 20
int b2 = B;
#undef A
int a2 = A;
if (1) {
#define C 30
int c = C;
}
int c2 = C;
return 0;
}
void test (){
int c3 = C;
}
预处理后我们得到如下预处理代码
这说说明了宏的作用域是根据声明的先后顺序决定的,与方法体无关,只要是在方法体后面引用且定义没有被取消,都是能够被替换的。
int main(void)
{
int a1 = 10;
int b1 = B;
int b2 = 20;
int a2 = A;
if (1) {
int c = 30;
}
int c2 = 30;
return 0;
}
void test (){
int c3 = 30;
}