【C语言】程序环境,预处理,编译,汇编,链接详细介绍,其中预处理阶段重点讲解
目录
程序环境
翻译环境
1. 翻译环境的两个过程
2. 编译过程的三个阶段
执行环境
预处理(预编译)
1. 预定义符号
2. #define
2.1 用 #define 定义标识符(符号)
2.2 用 #define 定义宏
2.3 #define 的替换规则
2.4 # 和 ## 的用法
2.5 宏和函数
2.6 #undef
3. 命令行定义
4. 条件编译
5. 文件包含
5.1 两种头文件的包含
5.2 嵌套文件包含
程序环境
在ANSI C(标准C)的任何一种实现中,存在两个不同的环境。
1. 翻译环境,在这个环境中源代码被转换为可执行的机器指令。
2. 执行环境,它用于实际执行代码。
.
我们写出的C语言代码是文本信息,计算机不能直接理解,计算机是执行二进制指令的,翻译环境负责将C语言代码转成二进制指令,执行环境负责执行二进制代码。
翻译环境
1. 翻译环境的两个过程
1. 一个工程可以有多个.c(源文件)文件,每个源文件都会单独经过编译器处理生成自己对应的目标文件(.obj),这个过程叫做编译。
2. 多个目标文件和链接库经过链接器的处理,最后生成可执行程序,这个过程叫做链接。
链接库的意思是链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
2. 编译过程的三个阶段
翻译环境分为编译和链接两部分,编译又有预处理,编译,汇编三个阶段。
1. 预处理:gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
2. 编译:gcc -S test.c
编译完成之后就停下来,结果保存在test.s中。
3. 汇编:gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中。
.
符号
1. 查看符号,这些符号都是全局的。
2. 每个源文件自己编译阶段符号汇总,汇编阶段形成符号表(符号,对应地址),链接进行所有源文件的符号表合并(相同符号合并,函数定义的符号的地址为有效地址,函数声明的符号的地址为无效地址,选取有效地址)。
比如:下图main.c符号表中_sum地址是无效的,合并的时候选择sum.c符号表的_sum地址。
执行环境
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排(例如单片机的烧录),也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack)也就是函数栈帧,存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
预处理(预编译)
1. 预定义符号
以下预定义符号会在预处理阶段被替换。
__FILE__ 替换为当前进行编译的源文件名称
__LINE__ 替换为当前的行号
__DATE__ 替换为文件被编译的日期
__TIME__ 替换为文件被编译的时间
__STDC__ 如果编译器遵循ANSI C,其值为1,否则未定义
.
2. #define
2.1 用 #define 定义标识符(符号)
语法:
#define name stuff
例子:
用 MAX 代表1000。
#define MAX 1000
为 register 这个关键字,创建一个简短的名字。
#define reg register
用更形象的符号来替换一种实现。
#define do_forever for(;;)
在写 case 语句的时候自动把 break 写上。
#define CASE break;case
如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,\__DATE__,__TIME__ )
提问:在define定义标识符的时候,要不要在最后加上;
答:不会直接报错,但没必要加,因为
1. 这只是单纯的替换,如果你加了分号,万一代码那边也写了分号,就会出现两个分号。
2. 不方便进行运算和逻辑执行,替换后你多一个分号有时候会影响原先代码的逻辑。
2.2 用 #define 定义宏
语法:
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中。
#define name(parament-list) stuff
用法:
#define MAX(x, y) (x>y ? x : y)
这个写法其实不够严谨,因为传进来的可能是多项式,所以尽量加上括号。
#define MAX(x, y) ((x)>(y) ? (x) : (y))
这个替换会发生在预处理阶段。
带副作用的宏参数
x+1;//不带副作用 x++;//带有副作用
例子
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )x = 5; y = 8; z = MAX(x++, y++);
z 就会替换为 z = ( (x++) > (y++) ? (x++) : (y++));
副作用就是表达式求值的时候出现的永久性效果。
2.3 #define 的替换规则
在程序中扩展 #define 定义的符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果有,那它们首先被替换,替换后的文本被插入到程序中原来文本的位置。
2. 然后参数被宏的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果有,就重复上述处理过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是宏不能出现递归。
2. 当预处理器搜索 #define 定义的符号时,字符串常量的内容并不被搜索。
2.4 # 和 ## 的用法
1. # 的用法:把宏的参数插入到字符串中。
例子:
#define PRINT(n) printf("the value of n is %d\n", n)int main() {int a = 10;PRINT(a);int b = 20;PRINT(b);return 0; }
问题:字符串中的n没有被替换。
解决办法:在 n 前面加一个 #
#define PRINT(n) printf("the value of "#n" is %d\n", n)int main() {int a = 10;PRINT(a);int b = 20;PRINT(b);return 0; }
题外话:字符串的双引号是可以连接的。
比如:
2. ## 的用法:可以把位于它两边的符号合成一个符号。
例子:
将 a 和 b 连起来变成 ab。
#define CAT(x, y) x##yint main() {int ab = 10;printf("%d\n", CAT(a, b));printf("%d\n", ab);return 0; }
2.5 宏和函数
宏的优点
1. 执行简单的运算时,选择用宏而不是函数。
原因:
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。
函数的参数必须声明为特定的类型,宏是类型无关的,所以函数只能在类型合适的表达式上使用。
2. 宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type) (type*)malloc(num*sizeof(type))MALLOC(10, int); //预处理器替换之后: (int*)malloc(10*sizeof(int));
宏的缺点
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏与函数对比
命名约定
1. 宏名全部大写
2. 函数名不要全部大写
2.6 #undef
这条指令用于移除一个宏定义。
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
例子:
3. 命令行定义
许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例子:
#include <stdio.h>int main(){int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("\n");return 0;}
编译指令:
gcc -D ARRAY_SIZE=10 programe.c
4. 条件编译
在编译一个程序的时候,条件编译指令可以将一条语句或一组语句进行编译或者放弃编译。
常见的条件编译指令:
1. 单分支的条件编译,常量表达式由预处理器求值。
#if 常量表达式//...#endif如: #define __DEBUG__ 1#if __DEBUG__//.. #endif
2. 多分支的条件编译
#if 常量表达式//... #elif 常量表达式//... #else//... #endif
3. 判断是否被定义,各自有两种写法。
#if defined(symbol)//... #endif#ifdef symbol//... #endif#if !defined(symbol)//... #endif#ifndef symbol//... #endif
4. 嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif #elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif #endif
5. 文件包含
5.1 两种头文件的包含
本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误。
库文件包含
#include <filename.h>
查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
题外话
1. 其实库文件包含也能用双引号,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
2. #include 指令可以使另外一个文件被编译,就像它实际出现于一样。
这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次。
5.2 嵌套文件包含
comm.h 和 comm.c 是公共模块。
test1.h 和 test1.c 使用了公共模块。
test2.h 和 test2.c 使用了公共模块。
test.h 和 test.c 使用了 test1 模块和 test2 模块。
这样最终程序中就会出现两份 comm.h 的内容,这样就造成了文件内容的重复。
解决办法有两种
1. 条件编译
#ifndef __TEST_H__#define __TEST_H__ ... //头文件的内容#endif
2. 头文件开头写:
#pragma once
这样就可以避免头文件的重复引入。
林宇恒/code_c - 码云 - 开源中国 (gitee.com)
相关文章:

【C语言】程序环境,预处理,编译,汇编,链接详细介绍,其中预处理阶段重点讲解
目录 程序环境 翻译环境 1. 翻译环境的两个过程 2. 编译过程的三个阶段 执行环境 预处理(预编译) 1. 预定义符号 2. #define 2.1 用 #define 定义标识符(符号) 2.2 用 #define 定义宏 2.3 #define 的替换规则 2.4 # 和 ## 的用法 2.5 宏和函数 2.6 #undef …...
人生低谷来撸C#--021 多线程
1、概念 线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。 线程是轻量级进程。一个使用线程的常见实例是现代操作系统中…...

【优秀python django系统案例】基于python的医院挂号管理系统,角色包括医生、患者、管理员三种
随着信息技术的迅猛发展,传统的医院挂号管理方式面临着效率低下、排队时间长、信息不对称等诸多问题。这些问题不仅影响患者的就医体验,也加重了医院工作人员的负担。在此背景下,基于Python的医院挂号管理系统应运而生。该系统旨在通过信息化…...

硬盘数据丢失不再怕,四大恢复工具帮你轻松逆转局面!
硬盘故障、误删文件、病毒攻击等原因导致数据丢失的情况时有发生。面对这种情况,如何高效、快速地进行硬盘数据恢复呢?接下来几款好用的数据恢复软件推荐给大家。 一、福昕数据恢复:全方位恢复,让数据无遗漏 链接:ww…...

自定义封装日历组件
自定义日历 工作需要,但现有框架封装的日历无法满足需求,又找不到更好的插件,所以准备自己封装一个。 效果图和说明 一个很简易版的demo日历,本文只提供最基本的功能代码,便于阅读二开。 新建calendar.vue文件 <…...
【大模型】【面试】独家总结表格
问题解答你能解释一下Transformer架构及其在大型语言模型中的作用吗?Transformer架构是一种深度神经网络架构,于2017年由Vaswani等人在他们的论文“Attention is All You Need”中首次提出。自那以后,它已成为大型语言模型(如BERT和GPT)最常用的架构。 Transformer架构使用…...

C# 6.定时器 timer
使用控件: 开启定时器:timer1.Start(); 关闭定时器:timer1.Stop(); 定时间时间间隔:Interval timer1.Interval 1000; Interva等于1000是每一秒刷新一次 定时器默认时间间隔是100ms 代码创建定时器 ①创建 Timer t1 new Timer(); …...
有了 createSlice,还有必要使用 createReducer 吗?什么情况需要 createReducer 呢?
通常情况下,使用 createSlice 已经足够满足大多数需求,而不需要直接使用 createReducer。但是,在某些特定场景下,createReducer 仍然有其用处: 更细粒度的控制: 当你需要对 reducer 的行为进行更精细的控制…...

怎么搭建AI带货直播间生成虚拟主播?
随着电商直播带货的热潮不断升温,虚拟主播逐渐崭露头角,成为电商直播领域的新宠,相较于真人主播,虚拟主播具备无档期风险、人设稳定可控、24小时不间断直播等显著优势。 本文将深入探讨如何搭建一个AI带货直播间,并详…...
设计模式的原则
设计模式的原则通常包括以下几种核心原则: 单一职责原则 (SRP):一个类应该只有一个单一的职责,即该类应该只有一个引起它变化的原因。这样可以减少类之间的耦合,使得系统更加易于维护和扩展。 开放/封闭原则 (OCP):软…...
RocketMQ与RabbitMQ的区别:技术选型指南
在现代分布式系统和微服务架构中,消息队列(Message Queue,简称MQ)扮演着至关重要的角色。消息队列用于实现系统间的异步通信、解耦、削峰填谷等功能。目前常见的MQ实现包括ActiveMQ、RabbitMQ、RocketMQ和Kafka。本文将重点对比Ro…...
小白也能懂:SQL注入攻击基础与防护指南
SQL注入是一种针对数据库的攻击方式,攻击者通过在Web表单、URL参数或其他用户输入的地方插入恶意SQL代码,以此绕过应用程序的验证机制,直接与后台数据库交互。这种攻击可以导致攻击者无授权地查看、修改或删除数据库中的数据,甚至…...

【Hot100】LeetCode—76. 最小覆盖子串
题目 原题链接:76. 最小覆盖子串 1- 思路 利用两个哈希表解决分为 :① 初始化哈希表、②遍历 s,处理当前元素,判断当前字符是否有效、③收缩窗口、④更新最小覆盖子串 2- 实现 ⭐76. 最小覆盖子串——题解思路 class Solution …...
删除排序链表中的重复元素 II(LeetCode)
题目 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 解题 class ListNode:def __init__(self, val0, nextNone):self.val valself.next nextclass Solution:def deleteDuplicates(self…...
【Java】解决如何将Http转为Https加密输出
目录 HTTP转HTTPS一、 获取 SSL/TLS 证书二、 安装证书2.1 Apache2.2 Nginx 三、更新网站配置四. 更新网站链接五. 检查并测试六. 自动续期(针对 Lets Encrypt) HTTP转HTTPS 将网站从 HTTP 转换为 HTTPS 能够加密数据传输,还能提高搜索引擎排…...

二叉树链式结构的实现(递归的暴力美学!!)
前言 Hello,小伙伴们。你们的作者菌又回来了,前些时间我们刚学习完二叉树的顺序结构,今天我们就趁热打铁,继续我们二叉树链式结构的学习。我们上期有提到,二叉树的的底层结构可以选为数组和链表,顺序结构我们选用的数…...

Python | Leetcode Python题解之第312题戳气球
题目: 题解: class Solution:def maxCoins(self, nums: List[int]) -> int:n len(nums)rec [[0] * (n 2) for _ in range(n 2)]val [1] nums [1]for i in range(n - 1, -1, -1):for j in range(i 2, n 2):for k in range(i 1, j):total v…...

远程访问mysql数据库的正确打开方式
为了安全,mysql数据库默认只能本机登录,但是在有些时候,我们会有远程登录mysql数据库的需求,这时候应该怎么办呢? 远程访问mysql数据,需要两个条件: 首先需要mysql服务器将服务绑定到0.0.0.0…...
网络6 -- udp_socket 实现 echo服务器
目录 1.server 服务端 1.1.完整代码展示: 1.2.代码解析: 1.2.1 给服务端创建套接字 1.2.2 绑定套接字 1.2.3 服务端接受数据并返回 2.客户端: 2.1 完整代码展示: 2.2 代码解析 2.2.1 客户端使用手则: 2.2.2 …...

ASUS/华硕幻15 2020 冰刃4 GX502L GU502L系列 原厂win10系统 工厂文件 带F12 ASUS Recovery恢复
华硕工厂文件恢复系统 ,安装结束后带隐藏分区,一键恢复,以及机器所有驱动软件。 系统版本:windows10 原厂系统下载网址:http://www.bioxt.cn 需准备一个20G以上u盘进行恢复 请注意:仅支持以上型号专用…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...