预处理笔记

定义

预处理令牌

预处理令牌(Token)是指预处理器在处理源代码时识别的基本单元。预处理令牌用于执行宏替换、条件编译和文件包含等操作。预处理令牌的主要类型包括:标识符(变量名、函数名)、关键字(#define、#include)、常量、运算符、分隔符。

逻辑代码行

代码中涉及实际业务逻辑的行,一个逻辑行中的所有Token应被视为一个单元来解析和处理。

空白字符

包括空格、制表符(Tab)、垂直制表符(Vertical Tab)、换行符、回车符、换页符。

预处理指示

一条预处理指示由一个逻辑代码行组成。以#开头,后面跟若干Token,在预处理指示的开头、结尾以及各Token之间可以包含任意数量的空格和制表符,但不允许出现其他空白字符​。

C编译器在进行语法解析之前的预处理步骤

  1. 连续的代码行通过续行符"\"合并成一个逻辑代码行。
  2. 注释替换成空格。
  3. 将逻辑代码行划分成不同的Token。
  4. 在字符常量或字符串字面值中,处理转义序列,用相应的字节替换。
  5. 在Token中识别出宏定义和预处理指示,进行宏展开,预处理。
  6. 去除空白字符。

编译并查看预处理结果 

g++ -E example.cpp -o example.i

生成一个名为example.i的文件,里面包含宏展开的代码。

生成汇编代码

g++ -S example.cpp -o example.s

生成的example.s文件会包含编译器生成的汇编代码。

编译代码并生成目标文件,指定优化级别

g++ -O0 -c example.cpp -o example_0.o # 无优化
g++ -O1 -c example.cpp -o example_1.o # 基本优化
g++ -O2 -c example.cpp -o example_2.o # 更高级优化

 生成不同优化级别的目标文件,如example_0.o,example_1.o,example_2.o。

生成反汇编代码

objdump -d example.o > example.asm

把example.o的反汇编代码输出到example.asm文件中。 

变量式宏定义

宏定义名可以像变量一样在代码中使用。

#define N 20
#define STR "hello, world"

函数式宏定义

宏定义名可以像函数一样在代码中使用。

// test.cpp
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
    int i = 5;
    int j = 10;
    int k = MAX(i & 0x0f, j & 0x0f);
    return 0;
}

函数式宏定义的参数没有类型,预处理器只负责进行形式上的替换,而不做参数类型检查。

对于真正的函数,编译器会生成一个函数体,调用时有传参指令和call指令。这种方式在编译时生成了额外的调用开销,但在多个地方调用同一个函数时,函数体只会出现一次。对于函数式宏定义,在预处理阶段被展开为具体的代码。预处理器在每次使用宏的地方展开宏定义,生成的代码直接包含宏体,可能导致目标文件增大。

定义这种宏最好不要省略括号,可能出现运算优先级错误。

宏定义在处理有副作用的表达式时可能会导致一些意外的结果,这主要是因为宏在预处理时只是简单地展开代码,而不是进行真正的函数调用。

eg. 可能导致的错误
#include <iostream>
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int a = 1;
    int b = 2;
    int result = MAX(++a, b);
    std::cout << "Result: " << result << std::endl;
    std::cout << "a: " << a << std::endl;

    a = 1;
    b = 2;
    result = max(++a, b);
    std::cout << "Result: " << result << std::endl;
    std::cout << "a: " << a << std::endl;
    return 0;
}

 

a的值可能会被递增两次,因编译器而异,这就导致了不可预期的行为。这是宏的一个潜在陷阱,尤其是当宏的参数有副作用时。

eg. 可能导致效率降低
#define MAX(a, b) ((a)>(b)?(a):(b))
#include <iostream>

int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };

int max(int n){
    return n == 0 ? a[0] : MAX(a[n], max(n-1));
}

int main(){
    std::cout<<max(9)<<std::endl;
    return 0;
}

 

a[n]被计算了两次,尤其是当a[n]是复杂的表达式时。 

eg. 函数式宏的常见形式
#include <stdio.h>

#define device_can_wakeup(dev) ((dev)->can_wakeup)
#define device_set_wakeup_enable(dev, val) ((dev)->wakeup_enabled = (val))

typedef struct {
    int can_wakeup;
    int wakeup_enabled;
} device_t;

#define device_init_wakeup(dev, val) \
do { \
    device_can_wakeup(dev) = !!(val); \
    device_set_wakeup_enable(dev, val); \
} while(0)

int main() {
    device_t my_device = {0, 0};  // 初始化设备

    device_init_wakeup(&my_device, 1);  // 使用宏设置设备唤醒功能

    
    printf("Device can wake up: %d\n", my_device.can_wakeup);
    printf("Device wakeup enabled: %d\n", my_device.wakeup_enabled);

    return 0;
}

do{...} while(0);是一种常见的技巧,用于确保宏在使用时总是作为一个单独的语句出现。使得宏在if语句等控制流结构中更安全,因为它在被展开时不会引入意外的代码块。

内联函数

#include <iostream>
inline int MAX(int a, int b){
    return a > b ? a : b;
}

int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };

int max(int n){
    return n == 0 ? a[0] : MAX(a[n], max(n-1));
}

int main(){
    std::cout<<max(9)<<std::endl;
    return 0;
}

生成的文件

# test5_0.0 elf64-x86-64

Disassembly of section .text:

0000000000000000 <_Z3maxi>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	89 7d fc             	mov    %edi,-0x4(%rbp)
   b:	83 7d fc 00          	cmpl   $0x0,-0x4(%rbp)
   f:	75 08                	jne    19 <_Z3maxi+0x19>
  11:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 17 <_Z3maxi+0x17>
  17:	eb 2f                	jmp    48 <_Z3maxi+0x48>
  19:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1c:	83 e8 01             	sub    $0x1,%eax
  1f:	89 c7                	mov    %eax,%edi
  21:	e8 00 00 00 00       	callq  26 <_Z3maxi+0x26>
  26:	89 c1                	mov    %eax,%ecx
  28:	8b 45 fc             	mov    -0x4(%rbp),%eax
  2b:	48 98                	cltq   
  2d:	48 8d 14 85 00 00 00 	lea    0x0(,%rax,4),%rdx
  34:	00 
  35:	48 8d 05 00 00 00 00 	lea    0x0(%rip),%rax        # 3c <_Z3maxi+0x3c>
  3c:	8b 04 02             	mov    (%rdx,%rax,1),%eax
  3f:	89 ce                	mov    %ecx,%esi
  41:	89 c7                	mov    %eax,%edi
  43:	e8 00 00 00 00       	callq  48 <_Z3maxi+0x48>
  48:	c9                   	leaveq 
  49:	c3                   	retq   

000000000000004a <main>:
  4a:	55                   	push   %rbp
  4b:	48 89 e5             	mov    %rsp,%rbp
  4e:	bf 09 00 00 00       	mov    $0x9,%edi
  53:	e8 00 00 00 00       	callq  58 <main+0xe>
  58:	89 c6                	mov    %eax,%esi
  5a:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 61 <main+0x17>
  61:	e8 00 00 00 00       	callq  66 <main+0x1c>
  66:	48 89 c2             	mov    %rax,%rdx
  69:	48 8b 05 00 00 00 00 	mov    0x0(%rip),%rax        # 70 <main+0x26>
  70:	48 89 c6             	mov    %rax,%rsi
  73:	48 89 d7             	mov    %rdx,%rdi
  76:	e8 00 00 00 00       	callq  7b <main+0x31>
  7b:	b8 00 00 00 00       	mov    $0x0,%eax
  80:	5d                   	pop    %rbp
  81:	c3                   	retq   

0000000000000082 <_Z41__static_initialization_and_destruction_0ii>:
  82:	55                   	push   %rbp
  83:	48 89 e5             	mov    %rsp,%rbp
  86:	48 83 ec 10          	sub    $0x10,%rsp
  8a:	89 7d fc             	mov    %edi,-0x4(%rbp)
  8d:	89 75 f8             	mov    %esi,-0x8(%rbp)
  90:	83 7d fc 01          	cmpl   $0x1,-0x4(%rbp)
  94:	75 32                	jne    c8 <_Z41__static_initialization_and_destruction_0ii+0x46>
  96:	81 7d f8 ff ff 00 00 	cmpl   $0xffff,-0x8(%rbp)
  9d:	75 29                	jne    c8 <_Z41__static_initialization_and_destruction_0ii+0x46>
  9f:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # a6 <_Z41__static_initialization_and_destruction_0ii+0x24>
  a6:	e8 00 00 00 00       	callq  ab <_Z41__static_initialization_and_destruction_0ii+0x29>
  ab:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # b2 <_Z41__static_initialization_and_destruction_0ii+0x30>
  b2:	48 8d 35 00 00 00 00 	lea    0x0(%rip),%rsi        # b9 <_Z41__static_initialization_and_destruction_0ii+0x37>
  b9:	48 8b 05 00 00 00 00 	mov    0x0(%rip),%rax        # c0 <_Z41__static_initialization_and_destruction_0ii+0x3e>
  c0:	48 89 c7             	mov    %rax,%rdi
  c3:	e8 00 00 00 00       	callq  c8 <_Z41__static_initialization_and_destruction_0ii+0x46>
  c8:	90                   	nop
  c9:	c9                   	leaveq 
  ca:	c3                   	retq   

00000000000000cb <_GLOBAL__sub_I_a>:
  cb:	55                   	push   %rbp
  cc:	48 89 e5             	mov    %rsp,%rbp
  cf:	be ff ff 00 00       	mov    $0xffff,%esi
  d4:	bf 01 00 00 00       	mov    $0x1,%edi
  d9:	e8 a4 ff ff ff       	callq  82 <_Z41__static_initialization_and_destruction_0ii>
  de:	5d                   	pop    %rbp
  df:	c3                   	retq   

Disassembly of section .text._Z3MAXii:

0000000000000000 <_Z3MAXii>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	89 75 f8             	mov    %esi,-0x8(%rbp)
   a:	8b 45 fc             	mov    -0x4(%rbp),%eax
   d:	3b 45 f8             	cmp    -0x8(%rbp),%eax
  10:	7e 05                	jle    17 <_Z3MAXii+0x17>
  12:	8b 45 fc             	mov    -0x4(%rbp),%eax
  15:	eb 03                	jmp    1a <_Z3MAXii+0x1a>
  17:	8b 45 f8             	mov    -0x8(%rbp),%eax
  1a:	5d                   	pop    %rbp
  1b:	c3                   	retq   
# test5_1.o elf64-x86-64

Disassembly of section .text:

0000000000000000 <_Z3maxi>:
   0:	85 ff                	test   %edi,%edi
   2:	75 07                	jne    b <_Z3maxi+0xb>
   4:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # a <_Z3maxi+0xa>
   a:	c3                   	retq   
   b:	53                   	push   %rbx
   c:	89 fb                	mov    %edi,%ebx
   e:	8d 7f ff             	lea    -0x1(%rdi),%edi
  11:	e8 00 00 00 00       	callq  16 <_Z3maxi+0x16>
  16:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 1d <_Z3maxi+0x1d>
  1d:	48 63 fb             	movslq %ebx,%rdi
  20:	39 04 ba             	cmp    %eax,(%rdx,%rdi,4)
  23:	0f 4d 04 ba          	cmovge (%rdx,%rdi,4),%eax
  27:	5b                   	pop    %rbx
  28:	c3                   	retq   

0000000000000029 <main>:
  29:	48 83 ec 08          	sub    $0x8,%rsp
  2d:	bf 09 00 00 00       	mov    $0x9,%edi
  32:	e8 00 00 00 00       	callq  37 <main+0xe>
  37:	89 c6                	mov    %eax,%esi
  39:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 40 <main+0x17>
  40:	e8 00 00 00 00       	callq  45 <main+0x1c>
  45:	48 89 c7             	mov    %rax,%rdi
  48:	e8 00 00 00 00       	callq  4d <main+0x24>
  4d:	b8 00 00 00 00       	mov    $0x0,%eax
  52:	48 83 c4 08          	add    $0x8,%rsp
  56:	c3                   	retq   

0000000000000057 <_GLOBAL__sub_I_a>:
  57:	48 83 ec 08          	sub    $0x8,%rsp
  5b:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 62 <_GLOBAL__sub_I_a+0xb>
  62:	e8 00 00 00 00       	callq  67 <_GLOBAL__sub_I_a+0x10>
  67:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 6e <_GLOBAL__sub_I_a+0x17>
  6e:	48 8d 35 00 00 00 00 	lea    0x0(%rip),%rsi        # 75 <_GLOBAL__sub_I_a+0x1e>
  75:	48 8b 3d 00 00 00 00 	mov    0x0(%rip),%rdi        # 7c <_GLOBAL__sub_I_a+0x25>
  7c:	e8 00 00 00 00       	callq  81 <_GLOBAL__sub_I_a+0x2a>
  81:	48 83 c4 08          	add    $0x8,%rsp
  85:	c3                   	retq   
# test5_2.o elf64-x86-64

Disassembly of section .text:

0000000000000000 <_Z3maxi>:
   0:	85 ff                	test   %edi,%edi
   2:	75 0c                	jne    10 <_Z3maxi+0x10>
   4:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # a <_Z3maxi+0xa>
   a:	c3                   	retq   
   b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  10:	53                   	push   %rbx
  11:	89 fb                	mov    %edi,%ebx
  13:	8d 7f ff             	lea    -0x1(%rdi),%edi
  16:	e8 00 00 00 00       	callq  1b <_Z3maxi+0x1b>
  1b:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 22 <_Z3maxi+0x22>
  22:	48 63 fb             	movslq %ebx,%rdi
  25:	5b                   	pop    %rbx
  26:	39 04 ba             	cmp    %eax,(%rdx,%rdi,4)
  29:	0f 4d 04 ba          	cmovge (%rdx,%rdi,4),%eax
  2d:	c3                   	retq   

Disassembly of section .text.startup:

0000000000000000 <main>:
   0:	48 83 ec 08          	sub    $0x8,%rsp
   4:	bf 08 00 00 00       	mov    $0x8,%edi
   9:	e8 00 00 00 00       	callq  e <main+0xe>
   e:	39 05 00 00 00 00    	cmp    %eax,0x0(%rip)        # 14 <main+0x14>
  14:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 1b <main+0x1b>
  1b:	0f 4d 05 00 00 00 00 	cmovge 0x0(%rip),%eax        # 22 <main+0x22>
  22:	89 c6                	mov    %eax,%esi
  24:	e8 00 00 00 00       	callq  29 <main+0x29>
  29:	48 89 c7             	mov    %rax,%rdi
  2c:	e8 00 00 00 00       	callq  31 <main+0x31>
  31:	31 c0                	xor    %eax,%eax
  33:	48 83 c4 08          	add    $0x8,%rsp
  37:	c3                   	retq   
  38:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
  3f:	00 

0000000000000040 <_GLOBAL__sub_I_a>:
  40:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 47 <_GLOBAL__sub_I_a+0x7>
  47:	48 83 ec 08          	sub    $0x8,%rsp
  4b:	e8 00 00 00 00       	callq  50 <_GLOBAL__sub_I_a+0x10>
  50:	48 8b 3d 00 00 00 00 	mov    0x0(%rip),%rdi        # 57 <_GLOBAL__sub_I_a+0x17>
  57:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 5e <_GLOBAL__sub_I_a+0x1e>
  5e:	48 8d 35 00 00 00 00 	lea    0x0(%rip),%rsi        # 65 <_GLOBAL__sub_I_a+0x25>
  65:	48 83 c4 08          	add    $0x8,%rsp
  69:	e9 00 00 00 00       	jmpq   6e <_GLOBAL__sub_I_a+0x2e>

内联函数的目的是将函数的代码直接嵌入到调用它的地方,从而避免函数调用的开销。MAX函数是内联的,在优化后的汇编代码中看不到对MAX函数的call指令,因为函数体已经被直接嵌入到使用它的地方。

内联函数和宏定义相比,有类型安全、调试友好、代码易维护、副作用控制、代码优化等优点。

预处理运算符

# 运算符

在函数式宏定义中,#用于将宏参数转换为字符串字面值,预处理器用引号把实参括起来成为一个字符串字面值,并且实参中的连续多个空白字符被替换成一个空格。

#include <iostream>
#include <cstdio> // 包含 FILE 和 fputs 的头文件
// 定义一个宏,将宏参数转化为字符串
#define STR(s) #s

int main() {
    const char* str = STR(hello    world); 
    std::cout << str << std::endl; 
    
    // 将标准输出流stdout赋值给FILE*类型的指针s, s指向标准输出流
    FILE* s = stdout; 

    const char* output1 = STR(strncmp("ab\"c\0d", "abc", '\4"') == 0); 
    const char* output2 = STR(: @\n); 
    
    // 将output字符串的内容写入到由s指向的流中
    fputs(output1, s); 
    fputs(output2, s); 

    return 0;
}

## 运算符
#include <iostream>
#define CONCAT(a, b) a##b

void concat() {
    std::cout << "concat function called!" << std::endl;
}

int main() {
    // 调用 concat 函数
    CONCAT(con, cat)();

    return 0;
}

可变参数

宏定义中可变参数的部分用__VA_ARGS__表示,在宏展开时和...对应的几个实参可以看成一个实参来替换掉__VA_ARGS__。

#include <stdio.h>
#define showlist(...) printf(__VA_ARGS__)
// report 宏,根据条件打印
#define report(test, ...) ((test) ? printf(#test "\n") : printf(__VA_ARGS__))

void concat() {
    printf("concat function called!");
}

int main() {
    showlist("The first, second, and third items.\n");

    int x = 10, y = 5;
    report(x > y, "x is %d but y is %d\n", x, y);

    return 0;
}

##__VA_ARGS__

##运算符的作用是,如果可变参数是空的,它会删除多余的逗号。

#include <iostream>
#include <cstdarg>  // va_list
#define DEBUGP(format, ...) printk(format, ##__VA_ARGS__)

// 假设这是日志打印函数
void printk(const char* format, ...) {
    va_list args; 
    va_start(args, format); // 初始化 va_list
    vprintf(format, args);  // 使用 vprintf 输出格式化的日志
    va_end(args);           // 结束 va_list 的使用
}

int main() {
    int errorCode = 404;

    // 使用 DEBUGP 宏打印有参数的日志
    DEBUGP("error code: %d\n", errorCode);

    // 使用 DEBUGP 宏打印没有参数的日志
    DEBUGP("operation complete\n");

    return 0;
}

内核函数printk类似于printf,也带有格式化字符串和可变参数,由于内核不能调用Iibc的库函数,所以另外实现了这样一个打印函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值