【C语言 |预处理指令】预处理指令详解(包括编译与链接)

news2024/11/16 7:37:30

目录

一、编译与链接

 1.翻译环境

          -预处理

          -编译

          -汇编

          -链接

2.执行环境

二、预定义符号

三、#define定义常量

四、#define定义宏

五、带有副作用的宏参数

六、宏替换的规则

七、 宏函数的对比

八、#和##

1.#运算符

2.##运算符

九、命名约定

十、#undef

十一、 命令行定义

十二、 条件编译

十三、 头文件的包含

1.本地头文件包含

2.库文件包含

十四、 其他预处理指令


一、编译与链接

在ANSI C的任何⼀种实现中,存在两个不同的环境

第一种呢就是翻译环境,顾名思义就是将源代码被转换为可执行的二进制指令

第二种呢就是执行环境,可用于实际执行代码,并且输出结果

然后我们再来说翻译环境,是如何将一段代码转换为可执行的二进制指令的呢

其实编译这一部分又分为了预处理(预编译),编译,汇编 

 在一个程序中可能会有多个.c文件,这些文件会单独的经过编译处理生成对应的目标文件

Windows环境下生成的目标文件后缀为.obj,Linux环境下生成的目标文件为.o

多个目标文件跟链接库一起经过链接器的处理最终生成可执行程序

链接库呢,它是指运行时库(支持程序运行的基本函数集合)第三方库

知道了上面的操作,我们就可以展开,成为了以下这个过程

 1.翻译环境

          -预处理

在预处理阶段,源文件和头文件会被处理为.i为后缀的文件,处理规则如下

  • 将所有的#define删除,并且展开所有宏定义
  • 处理所有的条件编译指令
  • 处理#include 预编译指令,将包含的头文件的内容插⼊到该预编译指令的位置
  • 删除所有的注释
  • 添加行号文件名标识, ⽅便后续编译器生成调试信息等
  • 或保留所有的#pragma的编译器指令,编译器后续会使用
经过预处理后的.i文件中不再包含宏定义,因为宏已经被展开并且包含的头文件都被插入到.i文件中

          -编译

编译过程就是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析及优化,⽣成相应的汇编代码⽂件
  1. 将源代码程序被输⼊扫描器进行词法分析,把代码中的字符分割成⼀系列 的记号(关键字、标识符、字⾯量、特殊字符等)
  2. 接下来语法分析器,将对扫描产⽣的记号进⾏语法分析,从而产生语法树。这些语法树是以表达式为节点的树
  3. 语义分析器来完成语义分析,即对表达式的语法层⾯分析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。

          -汇编

汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句⼏乎都对应⼀条机器指令
就是根据汇编指令和机器指令的对照表⼀⼀的进行翻译,也不做指令优化

          -链接

链接是⼀个复杂的过程,链接的时候需要把一堆文件链接在一起才生成可执行程序
链接过程主要包括:地址和空间分配,符号决议和重定位等这些步骤。
链接解决的是⼀个项⽬中多个文件、多模块之间互相调⽤的问题
地址修正的过程也被叫做:重定位

2.执行环境

  • 程序必须载⼊内存中。在有操作系统的环境中:⼀般这个由操作系统完成。
  • 在独⽴的环境中,程序的载⼊必须由手工安排,也可能是通过可执⾏代码置⼊只读内存成。
  • 程序的执⾏便开始。接着便调⽤main函数。
  • 开始执⾏程序代码。这个时候程序将使⽤⼀个运⾏时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使⽤静态(static)内存,存储于静态内存中的变量在程序的整个执行过程⼀直保留他们的值。
  • 终⽌程序。正常终⽌main函数;也有可能是意外终止

二、预定义符号

C语⾔设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的,所以运行速度更快
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义


三、#define定义常量

基本语法 

# define name stuff

当然所定义的类型没有限制,可以为了定义值,可以为了替换复杂名字,也可以为了省事,下面这几种都是正确的定义方法: 

#define MAX 1000

#define float f //为 float这个关键字,创建⼀个简短的名字

#define forever for(;;) //⽤更形象的符号来替换⼀种实现(死循环)

#define CASE break;case //在写case语句的时候⾃动把 break写上


#define DEBUG_PRINT printf("file:%s\tline:%d\t \
 date:%s\ttime:%s\n" ,\
 __FILE__,__LINE__ , \
 __DATE__,__TIME__ );

 /如果定义的过长,可以分成几行写,除了最后一行外,后⾯都加⼀个反斜杠  \  (续行符)

 

 所以宏的定义可以各式各样,给了我们很大的自由度,使我们能尽情去发挥自己想象

那么还有一个问题在定义定义的标识符的时候需不需要加;呢,答案是否定的,就比如说

#define PR printf("hehe");

int main()
{
    PR;        //加了分号就相当于 printf("hehe");;  容易发生错误
    
    return 0;
}

为了避免上述的这个错误,我们定义的标识符的时候不需要加;


四、#define定义宏

#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(define macro)
 #define name( list ) stuff

举个例子来说明

#define SUPP( x ) x * x


int main()
{
    SUPP(2,3);

    return 0;
}

SUPP就是我们定义的一个宏,将宏置于函数内部(等预处理的时候,会自动替换成表达式 x * x)

但同时会存在一些问题

#define SUPP( x ) x * x


int main()
{
    SUPP(2);  // 2 * 2  == 4

    SUPP(2+1);  // 2 + 1 * 2 + 1  == 5

    10 * SUPP(5+2);  //10 * 5 + 2 * 5 + 2  == 62

    return 0;
}

因为被定义的宏是预处理阶段所进行的,在预处理的时候直接替换函数中的表达式,所以难免会有许多操作符,优先级之类的问题,这个解决问题的方法就是在表达式加上对括号就解决了

#define SUPP( x) ( ( x ) + ( x ) )
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

五、带有副作用的宏参数

副作⽤就是表达式求值的时候出现的永久性效果,就好比说
a = 1;

//a赋值的同时自己的值也改变了

b = a++;  // a = 2,b = 1;

a+1;//不带副作⽤
a++;//带有副作⽤

拿下面这个例子来举例子

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

还记得我说了,宏是在预处理阶段替换宏为函数中的表达式:替换完了为

x = 5,y = 8;

MAX(x++,y++) 替换为 ((x++)>(y++)?(x++)(y++))
                      x=6   y=9         y=10
                       5 > 8假,执行y++

六、宏替换的规则

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

 


七、 宏函数的对比

宏通常被应用于执行简单的运算,比如在两个数中找出较⼤的⼀个时,更有优势⼀些
#define MAX(a, b) ((a)>(b)?(a):(b))

//定义宏和函数的两种方式

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

 我们从以下几个方面来分析宏 和 函数 的优缺点:

  1. 代码长度:#define所定义的宏,每次使用的时候都会被插入到程序中,除了特别小的宏以外,程序的长度会大幅度增长;而函数的代码只出现一个地方,调用都用同一份
  2. 执行速度:#define所定义的宏更快;二函数存在着调用和返回等额外的步骤速度会慢一些
  3. 操作符优先级:#define所定义的宏求值是在上下文的环境中,结果不可预测 会存在着很多的问题;而函数的参数只在函数调用时候将结果传给函数,表达式课预测
  4. 带有副作用的参数:#define所定义的宏参数可能会被替换多个位置,多次被计算,对值有着不可预测的结果;函数只在传参的时候求值易控制
  5. 参数类型:#define所定义的宏与类型无关,只要操作是合法的,可以适用于任何参数类型;函数的参数是与类型有关,如果类型不同,所需的函数也不同
  6. 调试:宏是不能调试的;函数是可以逐句逐条调试
  7. 递归:宏是不能递归的;函数是可以递归的

八、#和##

1.#运算符

先给大家补充一个知识点,字符串中包含的字符串两个会合成一个字符串

printf("haha""hehe");

//两个输出的结果相同

printf("hahahehe");
#运算符将宏的⼀个参数转换为字符串字面量。仅允许出现在带参数的宏的替换列表中“字符串化
#define PRI(n) printf("the value of "#n " is %d", n);


int main()
{

    PrT(6); //printf("the value of "#n " is %d", n);
    return 0;
}

结果为the value of n is 6

不难发现#n,将转换成了一个字符串


2.##运算符

把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。
## 被称为记号粘合
大家想想看如果要实现一个结果,不同的数据类型就得写不同的函数
int int_max(int x, int y)
{
 return x>y?x:y;
}
float float_max(float x, float y)
{
 return x>yx:y;
}

那如果使用##,一切都会变的很简单

#define GENERIC_MAX(type)		    \
type type##_max(type x,typey)		\
{									\
	return (x>y?x:y);				\
}									\
	GENERIC_MAX(int);   //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
	GENERIC_MAX(float); //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名

在预处理阶段,预处理中的所有type全部被替换 


九、命名约定

把宏名全部⼤写   ,函数名不要全部⼤写

十、#undef

这条指令用于移除⼀个宏定义
#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

可以看到 定义了一个MAX,正常打印完毕以后,#undef 移除这个宏定义,再次打印MAX就会报错


十一、 命令行定义

许多C 的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程

十二、 条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条件编译指令
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif


#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif


#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

十三、 头文件的包含

1.本地头文件包含

#include "filename"

拿双引号引用

查找策略:先在源⽂件所在⽬录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头文件,如果找不到就提示编译错误。

2.库文件包含

#include <filename.h>

拿单尖括号引用

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
也可以用双引号引用库文件,但是查找的效率就低些,这样也不容易区分是库文件还是本地文件
当一个文件中包含多个头文件,重复的头文会被在预处理的时候多次替换,大大降低了效率,所以我们在声明一个头文件的前面不妨可以加上这段代码
#ifndef __TEST_H__  \\当未定义这个头文件时才会执行下面
#define __TEST_H__

.....

#endif
#pragma once

这两种都可以避免头文件重复引进


十四、 其他预处理指令

# error
# pragma
# line
...
后面再给大家介绍

希望对你有帮助 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1621947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【服务器部署篇】Linux下Tomcat安装和配置

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0c;产…

Linux网络编程---Socket编程

一、网络套接字 一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。) 在通信过程中&#xff0c;套接字一定是成对出现的 套接字通讯原理示意图&#xff1a; 二、预备知识 1. 网络字节序 内存中的多字节数据相对于内存地址有大端和小端之分 小端法&…

Ubuntu终端常用指令

cat cat 读取文件的内容 1、ls 一、 1、ll 显示当前目录下文件的详细信息,包括读写权限,文件大小,文件生成日期等(若想按照更改的时间先后排序,则需加-t参数,按时间降序(最新修改的时间排在最前)执行: $ ll -t, 按时间升序执行: $ ll -t | tac): ll 2、查看当前所处路径(完整…

Qt中常用对话框

Qt中的对话框&#xff08;QDialog&#xff09;是用户交互的重要组件&#xff0c;用于向用户提供特定的信息、请求输入、或进行决策。Qt提供了多种标准对话框以及用于自定义对话框的类。以下将详细介绍几种常用对话框的基本使用、使用技巧以及注意事项&#xff0c;并附带C示例代…

node.js 解析post请求 方法一

前提&#xff1a;依旧以前面发的node.js服务器动态资源处理代码 具体见 http://t.csdnimg.cn/TSNW9为模板&#xff0c;在这基础上进行修改。与动态资源处理代码不同的是&#xff0c;这次的用户信息我们借用表单来实现。post请求解析来获取和展示用户表单填写信息 1》代码难点&…

全彩屏负氧离子监测站的使用

TH-FZ5在繁忙的都市生活中&#xff0c;我们往往忽视了一个至关重要的问题——空气质量。随着工业化的进程加速&#xff0c;空气污染已成为影响人们健康的一大隐患。为了实时监测和了解身边的空气质量&#xff0c;全彩屏负氧离子监测站应运而生&#xff0c;成为了我们守护呼吸健…

企业集成平台建设方案(技术方案+功能设计)

企业集成平台建设方案及重点难点攻坚 基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&…

稀碎从零算法笔记Day59-LeetCode: 感染二叉树需要的总时间

题型&#xff1a;树、图、BFS、DFS 链接&#xff1a;2385. 感染二叉树需要的总时间 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一棵二叉树的根节点 root &#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start 。在第 0 分钟…

25计算机考研院校数据分析 | 北京航空航天大学

北京航空航天大学(Beihang University)&#xff0c;简称北航&#xff0c;由中华人民共和国工业和信息化部直属&#xff0c;中央直管副部级建制&#xff0c;位列“双一流”、"211工程”、"985工程”&#xff0c;入选“珠峰计划”、"2011计划”、“111计划”、&qu…

STM32标准库ADC和DMA知识点总结

目录 前言 一、ADC模数转换器 &#xff08;1&#xff09;AD单通道 &#xff08;2&#xff09;AD多通道 二、DMA原理和应用 &#xff08;1&#xff09;DMA数据转运&#xff08;内存到内存&#xff09; &#xff08;2&#xff09;DMAAD多同道&#xff08;外设到内存&#x…

debian和ubuntu的核心系统和系统命令的区别

Debian和Ubuntu虽然有很深的渊源&#xff0c;都是基于Debian的发行版&#xff0c;但它们在核心系统和系统命令上还是有一些差别的。以下是一些主要的不同之处&#xff1a; 1. 发布周期&#xff1a; - Debian&#xff1a; Debian项目采用滚动发布模型&#xff0c;持续更新&a…

SpringCloud Alibaba--nacos配置中心

目录 一.基础介绍 1.1概念 1.2 功能 二.实现 2.1 依赖 2.2 新建配置文件 2.3 克隆 2.4 配置bootstap.yml文件 三.测试 一.基础介绍 1.1概念 在微服务架构中&#xff0c;配置中心就是统一管理各个微服务配置文件的服务。把传统的单体jar包拆分成多个微服务后&#xf…

到底什么是认证

认证和授权 什么是认证 认证 (Authentication) 是根据凭据验明访问者身份的流程。即验证“你是你所说的那个人”的过程。 身份认证&#xff0c;通常通过用户名/邮箱/手机号以及密码匹配来完成&#xff0c;也可以通过手机/邮箱验证码或者生物特征&#xff08;如&#xff1a;指纹…

LangChain的核心模块和实战

主要模型 LLM:对话模型, 输入和输出都是文本Chat Model: 输入输出都是数据结构 模型IO设计 Format: 将提示词模版格式化Predict: langchain就是通过predict的方式调用不同的模型, 两个模型的区别不大, Chat Model 是以LLM为基础的.Parese: langchain还可以对结果进行干预, 得…

css盒子设置圆角边框的方法

前言 欢迎来到我的博客 个人主页&#xff1a;北岭敲键盘的荒漠猫-CSDN博客 本文为我整理的设置圆角边框的方法 需求描述 我们在设置盒子边框时&#xff0c;他总是方方正正的。 我们想让这个直直的边框委婉一点该怎么办呢。这个就提到了我们这篇文章讲的东西&#xff1a; bord…

二分查找知识点及练习题

知识点讲解 一、没有相同元素查找 请在一个有序递增数组中&#xff08;不存在相同元素&#xff09;&#xff0c;采用二分查找&#xff0c;找出值x的位置&#xff0c;如果x在数组中不存在&#xff0c;请输出-1&#xff01; 输入格式 第一行&#xff0c;一个整数n&#xff0c;代…

DevOps(十二)Jenkins实战之Web发布到远程服务器

前面两篇博文介绍了怎么将django开发的web应用推送到gitlab源码仓库&#xff0c;然后jenkins服务器从gitlab仓库拉下来&#xff0c;布署到jenkins服务器上&#xff0c;并用supervisor进行进程管理&#xff0c;保证web应用一直能正常运行&#xff0c;今天我们继续优化&#xff0…

政安晨:【深度学习神经网络基础】(十三)—— 卷积神经网络

目录 概述 LeNet-5 卷积层 最大池层 稠密层 针对MNIST数据集的卷积神经网络 总之 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎…

绿色便携方式安装apache+mysql+tomcat+php集成环境并提供控制面板

绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境 目录 绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境[TOC](目录) 前言一、XAMPP二、安装和使用1.安装2.使用 三、可能的错误1、检查端口占用2、修改端口 前言 安装集成环境往往配置复杂&#xff0c…

Gradio 最快创建Web 界面部署到服务器并演示机器学习模型,本文提供教学案例以及部署方法,避免使用繁琐的django

最近学习hugging face里面的物体检测模型&#xff0c;发现一个方便快捷的工具&#xff01; Gradio 是通过友好的 Web 界面演示机器学习模型的最快方式&#xff0c;以便任何人都可以在任何地方使用它&#xff01; 一、核心优势&#xff1a; 使用这个开发这种演示机器学习模型的…