【c语言】预处理

news2025/1/16 0:59:44

在这里插入图片描述

🚀write in front🚀
📜所属专栏:> c语言学习
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!

在这里插入图片描述

文章目录

  • 前言:
  • 一、详解编译与链接:
    • 1.程序的翻译环境与执行环境:
    • 2.翻译环境:
    • 3.翻译阶段:
      • ①.编译:
        • 预编译:
        • 编译:
        • 汇编
          • 符号表:
      • ②.链接:
    • 4.运行环境:
  • 二、预处理详解:
    • 1.预定义符号:
    • 2.#define:
        • ①. #define 定义标识符:
        • ②.#define 定义宏:
        • ③. #define 替换规则:
  • 总结:

前言:

一、详解编译与链接:

1.程序的翻译环境与执行环境:

  在研究程序的编译与链接细节之前,我们首先要了解我们程序的翻译以及执行环境,我们要知道,在 ANSI C 的任何一种实现中,都存在着两种环境:

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。

2.翻译环境:

  在翻译环境中执行的操作,简单来说可以分为三个步骤

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码**(.obj)。
  • 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入链接库(标准C函数库)中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中。

在这里插入图片描述
每次编译运行完成以后,我们都可以在所在文件里发现.exe文件和.obj文件

在这里插入图片描述

3.翻译阶段:

  翻译阶段又可以分为两个阶段,即编译链接
在这里插入图片描述

①.编译:

预编译:

首先我们先一段普通的代码:
在这里插入图片描述
然后用gcc做以下操作:

  • 预处理 选项 gcc -E test.c -o test.i
  • 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中

就会发现下面的情况:
在这里插入图片描述
预处理的作用:

头文件的包含、#define 定义符号的替换、注释的删除,文本操作。

编译:

用gcc选项 gcc -S test.c
编译完成之后就停下来,结果保存在test.s中,我们便会发现汇编代码

编译的作用

编译阶段就是将 C 语言的代码翻译成汇编代码,其中包含了语法分析、词法分析、语义分析、符号汇总(后面会重点讲到)等。

汇编

用gcc选项gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中,出现乱码(2进制码以文本形式无法看懂)

汇编的作用:

将汇编代码翻译成二进制的指令(存放到目标文件)符号汇总后**:形成符号表

符号表:

一个类似于这样的表格:
在这里插入图片描述
在这里,我们的符号表里是该程序里所有的函数名和每个函数所在的的地址,(对于使用的其他文件的函数,地址都先计为0x00,在后面链接阶段在修改该地址)

②.链接:

编译的作用是:

合并段表
合并符号表
重定位符号表

这里就是对所有文件的一个汇总,对于一个程序里,一个源文件使用了另一个源文件的函数,通过链接,就会将函数的地址汇总,并且改掉地址为0的函数。

4.运行环境:

在这个环境下,我们的程序就真正进入了运行阶段。我们程序的执行过程可以简述为下面四个步骤

  • ①. 程序载入内存中:在有操作系统的环境中该步骤通常由操作系统完成;而在独立环境中则必须由我们自己手动完成;独立环境中的程序也有可能通过执行可执行代码置入只读内存来完成。

  • ②. 调用 main 函数:程序正式开始执行。

  • ③. 顺序执行程序代码:在这个阶段中,我们的程序会使用一个运行时堆栈来存储函数的局部变量和返回地址。同时程序也可以使用静态内存来存储变量,并且这些存储于静态变量中的变量在整个程序的执行过程中将始终保留它们的值。

  • ④. 终止程序:一般情况下会正常终止 main 函数,但我们的程序也有可能会意外终止。

二、预处理详解:

1.预定义符号:

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

这些预定义符号都是语言内置的。
举个栗子:
通过:

printf("time:%s %s\n", __DATE__, __TIME__);

你就可以知道编译这段代码的当前时间

2.#define:

  #define 的用处非常多,就比如我们常用的定义标识符常量、定义宏等等,而在这个过程中,也有一些细节值得我们去注意。

①. #define 定义标识符:

我们常常会使用 #define 去定义一些标识符来方便我们的代码书写:

语法:
 #define name stuff

定义了之后,只有我们看到 name 这个东西,就给他一比一替换成 stuff这个东西就行即可

举个栗子:

#define MAX 1000
#define reg register
//为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)
//用更形象的符号来替换一种实现
#define CASE break;case
//在写case语句的时候自动把 break写上。
 
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                           date:%s\ttime:%s\n",\
						__FILE__,__LINE__,\
						__DATE__,__TIME__) 

注意:
  在 #define 的最后,最好不要加上分号 “ ; ” 加上可能会出现两个;号,可能会导致语法错误:

②.#define 定义宏:

  在#define 的机制中,包括了一个规定,这个规定允许把参数替换到文本中,这种实现通常称为定义宏(或简称为宏)。

宏的申明方式为:

#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号列表,且可能出现在 stuff 中

注意:
注意:
参数列表的左括号必须与name紧邻
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

易错点:

初学者在学习宏时很容易出现以下问题:
举个栗子:

#define SQUARE( x ) x * x
int main
{
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
}

乍一看,你可能觉得这段代码将打印36这个值。
事实上,它将打印11.
为什么?

我们前面说过了,标识符的替换是一比一替换
替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf (“%d\n”,a + 1 * a + 1 ),所以结果是11。

所以我们可以对这个宏进行修改:

#define SQUARE(x) (x) * (x)

由此可见,我们在进行宏定义的时候,一定要舍得加括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

③. #define 替换规则:

#define 在进行符号替换时,遵循以下规则

  1. 调用宏时首先对参数进行检查,检查是否包含任何由 #define 定义的符号。如果有,它们将首先被替换
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果有,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

总结:

  这就是程序环境和预处理的相关知识。当然,这里对于宏的讲解是往往不够的!宏的相关知识将在下一篇文章中进行详细讲解!更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
c语言学习
算法
智力题
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

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

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

相关文章

设计模式--工厂模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。   工厂模式主要使用了C的多态特性。将存在继承关系的类&a…

通过一张照片来定位拍摄地点和网站的域名 LA CTF 2023

简介 这次打ctf遇到了一个比较经典的osint类题目,在这里分享一下如何做此类题目 题目链接: https://platform.lac.tf/challs题目简介: 你能猜出这个猫天堂的名字吗?答案是此位置的网站域。例如,如果答案是 ucla&…

浅谈分布式锁的原理

1.业务场景引入 在进行代码实现之前,我们先来看一个业务场景: 系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户…

SPINAND UBI 离线烧录 开发指南

SPINAND UBI 离线烧录 开发指南 1 概述 编写目的: 介绍Sunxi SPINand 烧写时的数据布局 2 名词解释 词义UBIunsorted block imagePEBphysical erase blockLEBlogical erase block PEB 和logical block 关系 1 PEB 1 logical block 1 logical block 2 physical blocks3 总…

React从入门到精通二

React从入门到精通之购物车案例1. 购物车需求说明使用到的data list2. 项目code1. 购物车需求说明 list data展示到列表中每个item的通过按钮来控制购买的数据量删除按钮可以删除当前的itemTotal Price计算当前购物车的总的价格 使用到的data list const books [{id: 1,name…

OAK相机深度流探测草莓距离

编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多👍⭐️✍ 内容可能会不定期更新,官网内容都是最新的,请查看首发地址链接。 ▌前言 Hello,大家好,这里是OAK中国,我是助手…

uniapp 悬浮窗(悬浮球、动态菜单、在其他应用上层显示) Ba-FloatBall

简介(下载地址) Ba-FloatBall 是一款在其他应用上层显示的悬浮球插件。支持展示菜单、拖动、自动贴边等;支持自定义样式。 支持添加展示菜单,可自定义(不添加菜单,可只显示悬浮球)支持自定义悬…

一口吃不成ChatGPT,复旦版MOSS服务器被挤崩后续

ChatGPT 是目前最先进的 AI,由于 ChatGPT 的训练过程所需算力资源大、标注成本高,此前国内暂未出现对大众开放的同类产品。 适逢ChatGPT概念正火,2 月 21 日,复旦团队发布首个中国版类 ChatGPT 模型「MOSS」,没想到瞬时…

Python-生成列表

1.生成列表使用列表前必须先生成列表。1.1使用运算符[ ]生成列表在运算符[ ]中以逗号隔开各个元素会生成包含这些元素的新列表。另外,如果[ ]中没有元素就会生成空列表示例>>> list01 [] >>> list01 [] >>> list02 [1, 2, 3] >>…

云、安全、网络三位一体,Akamai 推出大规模分布式边缘和云平台 Akamai Connected Cloud

出品 | CSDN 云计算 云服务市场规模在持续增长。 基于网络技术积累与优势,与布局边缘计算之后,巨头 Akamai 在继续推进它的技术与产品进程。近日,Akamai 正式推出大规模分布式边缘和云平台 Akamai Connected Cloud,包含云计算、安…

软考学习笔记(题目知识记录)

答案为 概要设计阶段 本题涉及软件工程的概念 软件工程的任务是基于需求分析的结果建立各种设计模型,给出问题的解决方案 软件设计可以分为两个阶段: 概要设计阶段和详细设计阶段 结构化设计方法中,概要设计阶段进行软件体系结构的设计&…

学生管理系统-课后程序(JAVA基础案例教程-黑马程序员编著-第六章-课后作业)

【案例6-2】 学生管理系统 【案例介绍】 1.任务描述 在一所学校中,对学生人员流动的管理是很麻烦的,本案例要求编写一个学生管理系统,实现对学生信息的添加、删除、修改和查询功能。每个功能的具体要求如下: 系统的首页&#…

视频技术基础知识

一、视频图像基础 像素:图像的基本单元,即一个带有颜色的小块分辨率:图像的大小或尺寸,用像素个数来表示。原始图像分辨率越高,图像就越清晰位深:存储每位像素需要的二进制位数;位深越大&#…

JAVA线程入门简介

线程入门简介什么是程序?什么是进程?什么是线程?单线程与多线程并发与并行线程的使用用java查看有多少个cpu创建线程的两种方式继承Thread类,重写run方法实现Runnable接口,重写run方法多线程机制为社么是start?源码解析什么是程序? 是为完…

防错料使用二维码解决方案 生产过程物料防错管理

生产过程中,物料的防错管理是非常重要的一环。它能够有效地防止物料错用或混用,从而降低产品质量问题的发生率,减少生产成本和生产周期,提高生产效率和产品质量。以下是生产过程物料防错管理的具体措施:1.明确物料标识…

SpringBoot Data Redis来操作Redis

SpringBoot Data Redis来操作Redis1、Redis启动Redis主要的作用安装的位置启动2、Java中来操作Redis3、Spring Data Redis(重点)测试连接配置Redis序列化器redisTemplate操作常见数据类型通用操作,针对不同的数据类型都可以操作申明: 未经许可&#xff0…

浅谈Springboot自动化配置原理

文章目录1.前言2.SpringBoot的入口3.SpringBootApplication背后的秘密4.Configuration5.ComponentScan扫描bean6.EnableAutoConfiguration7.自动配置生效1.前言 不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项。除了某些老旧的政…

java面试题-JUC线程池

1.FutureTask的作用?FutureTask 是 Java 并发编程中的一个类,用于异步执行任务并获取其结果。它实现了 Future 和 Runnable 接口,因此可以作为一个可运行的任务提交给 Executor 执行,也可以通过 Future 接口获取任务执行的结果。FutureTask …

2023年DAMA-CDGA/CDGP数据治理认证选择哪家机构好?

DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…

将整数数组变为浮点型数组的np.asfarray()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将整数数组转换为浮点型数组 np.asfarray() 选择题 关于以下代码说法错误的一项是? import numpy as np a1 np.array([1,2,3]) print("【显示】a1",a1) print("【执行】a…