预处理详解

简介:

@[TOC]

前言

  • 本文介绍编译4大过程中的预处理
  • 博主的问题收集与自我解答:New Young
  • 转载请标明出处:New Young

预处理操作

转换逻辑行

当一行不足以续写程序时,我们通过‘ \ ',将逻辑行转换成多个物理行

image-20220125150638325

删除注释

去除程序中的注释

执行预处理指令

预处理指令从#开始,到第一个换行符结束。

预处理指令有#define,#if ,#if defined()等

这里重点介绍 明示常量:#define

预处理指令 #define

define定义

define 定义的指令(逻辑行)由3部分组成:

  • 预处理指令#define本身
  • 选定符号的缩写---也称为
约定:宏名全部大写。

根据宏的不同 ,有些宏称为 :类对象宏,这一些宏代表值,有些宏称为 :类函数宏

宏的命名有严格要求:不能有空格,完全按照c变量的命名规则:只能用字符,数字,下划线,且首字符不能是数字。

  • 替换列表或者替换体.这部分可以是是常量值,也可以是类函数的代码。
  • image-20220125152317803
  • 注意:在宏和替换体之间必须有一个 空格或者tabl。同时在定义的最后最好不要加”;“—-因为文本替换后,可能会导致代码出现问题。

define使用规则

  • 在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
    述处理过程

define 的注意点

  1. 预处理器对预处理指令define ,直接进行文本替换,不做计算,不对表达式求值,
  2. define可以在程序的然后地方定义,但必须是一行的起始处。
  3. 对于宏的替换体,建议多使用括号,否则运算的优先级可能会有问题

    image-20220125160206275

  4. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归

    image-20220125160926000

  5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索 。

    image-20220125160700290

  6. 不要在宏中使用可能产生副作用的参数。

    image-20220125161622203

宏参数创建字符串:#运算符

image-20220125164158794

黏合剂:##运算符

image-20220125164745321

define和函数

优点

宏通常被应用于执行简单的运算 如:

#define MAX(a, b) ((a)>(b)?(a):(b))
  1. 我们知道函数在调用进行函数栈帧时,需要进行的准备工作可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

缺点

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。

    • 调试是编译阶段,经过预处理的文本替换,调试时期的某些访问是非法的。

      image-20220125162954577

  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错
对象 代码长度 执行速度 操作符优先级 带有副作用的参数 参数类型 调试 递归
define定义的类函数宏 每次使用时,宏代码都会被插入到程序中。除了非 常小的宏之外,程序的长度会大幅度增长 更快 宏参数的求值是在所有周围表达式的上下文环境 里,除非加上括号,否则邻近操作符的优先级可能 会产生不可预料的后果,所以建议宏在书写的时候 多些括号。 参数可能被替换到宏体中的多个位置,所以带有副 作用的参数求值可能会产生不可预料的结果 宏的参数与类型无关,只要对参数的操作是合法 的,它就可以使用于任何参数类型 宏是不方便调试的 宏是不能递归的
函数 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码,进行函数栈帧 更快 函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测 函数参数只在传参的时候求值一 次,结果更容易控制 宏的参数与类型无关,只要对参数的操作是合法 的,它就可以使用于任何参数类型 函数是可以逐语句调试的 函数是可以递归的

define和typedef

  • typedef一般用于为了方便读写某些复杂数据类型(结构体类型等),而重命令了这些复杂类型,而define 的使用范围更广,不过也有一些区别。
  • define 定义最后的 ”;“,一般不建议加,但是 typedef一定加

image-20220125165926436

define和 const

如果想保护数据而安全,不允许程序修改其值,可以通过ANSIC C类型限定符:以const修饰的对象,不能通过赋值,递增,递减来改变其值。

image-20220125171047653

define和enum

虽然define定义的类对象宏是全局的,可以随时的进行改变。但是不能用与调试,而enum定义的枚举常量却可以,且一次可以定义多个全局的常量。因此当需要多次修改某个数据时,建议用enum进行定义。

image-20220125172629337

预处理指令#undef

当不确定一个宏是否被定义过了,可以通过 #undef type_name 来移除一个宏定义,再重新定义这个宏。

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

预定义宏

某些宏是c语言内置已经定义好的,不能取消定义。如:

__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

条件编译

在编译过程中,可以通过条件编译指令来选择将某段代码放到程序中

  1. ifdef=#if defined(symbol),#else,#endif指令

image-20220125175916737

  1. ifndef =#if !defined(symbol)

image-20220125175937646

文件包含

本地文件:用“”include的文件

#include "file.name"

image-20220125180629314

库文件:用<>include的文件

#include <stdio.h>

image-20220125180534322

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方
一样。这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次

文件包含方式

如果是本地文件:程序会先从当前目录去找该文件,如果没有再从库文件查找。

如果是库文件,直接从库文件查找。

嵌套文件包含

如果出现这种

image-20220125180900146

comm.h和comm.c是公共模块。test1.h和test1.c使用了公共模块。test2.h和test2.c使用了公共模块。test.h和test.c使用了test1模块和test2模块。这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复 。

解决方案:

每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__

或者:(推荐这个)

#pragma once

就可以避免头文件的重复引入

pragma pack()

重新设置 程序默认对齐数。

image-20220125181412440

相关文章
|
7月前
|
编译器 C++
C 预处理器
C 预处理器。
91 10
|
2月前
|
编译器 Linux C语言
|
7月前
|
编译器 C语言
预处理深入
预处理深入
43 0
预处理深入
|
7月前
|
编译器 C语言
c预处理器
c预处理器
48 0
|
7月前
|
Linux C语言 Windows
C预处理分析
C预处理分析
48 2
|
7月前
|
编译器 C++
c++预处理器
c++预处理器
42 0
|
安全 编译器 C语言
详解预处理(1)
详解预处理(1)
83 1
|
编译器 Linux C语言
详解预处理(2)
详解预处理(2)
80 0
预处理的学习
预处理的学习
61 0