【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盘进行恢复 请注意:仅支持以上型号专用…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...