学C的第三十四天【程序环境和预处理】
=========================================================================
相关代码gitee自取:
C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客
=========================================================================
1 . 程序的翻译环境和执行环境
在ANSI C(C语言标准)的任何一种实现中,存在两个不同的环境。
(1). 翻译环境:
在这个环境中源代码被转换为可执行的机器指令
计算机能够执行二进制指令,
但我们平常写的C语言代码是文本信息,计算机不能直接执行,
在翻译环境就可以把C语言代码(源代码)翻译为二进制指令(可执行的机器指令)
(2). 执行环境:
在这个环境下可以执行二进制的代码(实际执行代码)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 . 翻译环境
翻译环境包括 编译 和 链接 ,
编译又包括 预编译(预处理) 、编译、汇编,
(1). 翻译环境下的 编译 和 链接:
- 组成一个程序的每个源文件通过编译过程(各自进行编译)分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
图解:
(2). 编译的几个阶段:
在VS这种集成开发环境下不方便观察编译各阶段细节,
在Linux系统下使用gcc编译器更好观察
(2.1). 预编译(预处理) 阶段
生成文件后缀: xxx.i
该阶段会进行一些文本操作
包括
- 注释的删除
- #include 头文件的包含
- #define 符号的替换
- 所有预处理指令都是在预处理阶段处理的
图解:
(2.2). 编译阶段
生成文件后缀: xxx.s
该阶段会把C语言代码翻译为汇编指令
包括
- 语法分析
- 词法分析
- 语义分析
- 符号分析
图解:
(2.3). 汇编阶段
生成文件后缀: xxx.o (object目标文件)
该阶段会将编译阶段完成的汇编指令翻译为二进制指令
在编译阶段会进行符号汇总,
该阶段则会形成对应的符号表,
以便链接时使用
图解:
(3). 链接:
对编译生成的目标文件进行操作,
生成可执行程序(也是二进制文件)
包括
- 合并段表
- 符号表(由编译的汇编阶段形成)的合并和符号表的重定位
图解:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 . 预处理详解
(1). 预定义符号
C语言中预定义了一些符号,这些预定义符号都是语言内置的:
- __FILE__ ---- 进行编译的源文件
- __LINE__ ---- 文件当前的行号
- __DATE__ ---- 文件被编译的日期
- __TIME__ ---- 文件被编译的时间
- __STDC__ ---- 如果编译器遵循ANSI C(C语言标准),其值为1,否则未定义
示例:
(2). #define
(2.1). #define 定义标识符
写法:
#define name stuff
- name -- 定义的标识符名称
- stuff -- 定义的标识符内容
示例:
在define定义标识符的时候,最好不要在最后加上“分号 ;”
因为有些编译器可能会把“分号;”也当作stuff(标识符的内容)
示例:
(2.2). #define 定义宏
写法:
#define name( parament-list ) stuff
- name -- 定义的宏的名称
- parament-list -- 参数列表,参数会替换放到 stuff 中
- stuff -- 定义的宏的内容
注意:
定义宏时,
参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,
参数列表就会被解释为stuff的一部分。
示例:
注意:
用于对数值表达式进行求值的宏定义都应该用上图这种方式加上括号,
即对stuff中的各个参数分别加上括号 和 对stuff整体加上括号,
避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
(操作符优先级问题)。
(2.3). #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。
(参数列表中有其它#define定义的符号)
如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。(参数列表替换stuff中的内容)
对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。
如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。
但是对于宏,不能出现递归。(和函数的区别)- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
示例:
(2.4). # 和 ##
# 和 ## 这两个符号只能在宏里面使用
#:
该符号可以把宏的参数以字符串的形式插入到字符串中
示例:
##:
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
注意:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
示例:
(2.5). 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,
那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。
副作用就是表达式求值的时候出现的永久性效果。
示例:
(2.6). 宏和函数对比
属 性 #define定义宏 函数 代 码
长 度
每次使用时,宏代码都会被插入到程序中。
除了非常小的宏之外,程序的长度会大幅度增长
函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 执 行
速 度
更快 存在函数的调用和返回的额外开 销,所以相对慢一些 操作符 优先级 宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 带有副 作用的 参数 参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果。 函数参数只在传参的时候求值一 次,结果更容易控制。 参 数
类 型
宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。 调 试 宏是不方便调试的 函数是可以逐语句调试的 递 归 宏是不能递归的 函数是可以递归的
(2.7). 命名约定
一般来讲,函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。
所以我们从书写上进行区分:
- 宏名全部大写,
- 函数名不要全部大写
(3). #undef
这个指令用于移除一个宏定义,移除之后不能再使用
写法:
#undef NAMENAME
- NAME -- 要移除的宏的名字
示例:
(4). 命令行定义
许多C的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程
(VS不行,gcc可以)
例如:
当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性会有点用处。
(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,
我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。
这时候就可以用命令行定义灵活调整)
示例:
(5). 条件编译
使用条件编译指令可以决定一条(一组)语句是否进行编译,
实现选择性的编译,
条件编译指令如果为真,则编译时内容保留
条件编译指令如果为假,则编译时内容删除
常见的条件编译指令:
指令一:单个分支的条件编译
#if 常量表达式 //一条(一组)语句... #endif //常量表达式由预处理器求值。
示例:
指令二:多个分支的条件编译
#if 常量表达式//...#elif 常量表达式//...#else//...#endif
示例:
指令三:判断是否被定义的条件编译
//如果定义过:#if defined(symbol) //第一种写法#ifdef symbol //第二种写法//如果未定义过:#if !defined(symbol) //第一种写法#ifndef symbol //第二种写法//symbol -- 定义的符号
示例:
指令四:嵌套指令
将上面的三种条件编译组合使用
示例:
(6). 文件包含
#include指令 可以使另外一个文件被编译。
就像它实际出现于 #include指令 的地方一样。
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次。
头文件被包含的方式:
本地文件包含
写法:
#include "filename"
查找策略:
先在源文件所在目录下查找,如果该头文件未找到,
编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
库文件包含
写法:
#include <filename.h>
查找策略:
查找头文件直接去标准路径下去查找,
如果找不到就提示编译错误。
嵌套文件包含:
如果一个文件有很多头文件,
另一个文件包含了该文件,同时该文件也有头文件,
再有一个文件包含这两个头文件,
那么同一个头文件就有可能重复出现在一个文件中
可以使用条件编译来防止头文件重复出现:
//每个头文件的开头写:#ifndef __TEST_H__#define __TEST_H__//头文件的内容#endif //__TEST_H__//或者:#pragma once//头文件的内容
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 . 运行环境
程序执行的过程:
1. 程序必须载入内存中
在有操作系统的环境中:程序载入内存一般这个由操作系统完成。
在独立的环境中,程序的载入必须由手工安排,
也可能是通过可执行代码置入只读内存来完成。
2.
程序的执行开始,接着便调用main函数。
3. 开始执行程序代码
这个时候程序将使用一个运行时堆栈(stack)(函数栈帧)
,存储函数的局部变量和返回地址。
程序同时也可以使用静态(static)内存,
存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序
正常终止main函数;也有可能是意外终止。
相关文章:

学C的第三十四天【程序环境和预处理】
相关代码gitee自取: C语言学习日记: 加油努力 (gitee.com) 接上期: 学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客 1 . 程序的翻译环境和执行环境 在ANSI C(C语言标准)的任何一种实现中,存在两个不同的环境。 ࿰…...

微服务中间件--Ribbon负载均衡
Ribbon负载均衡 a.Ribbon负载均衡原理b.Ribbon负载均衡策略 (IRule)c.Ribbon的饥饿加载 a.Ribbon负载均衡原理 1.发起请求http://userservice/user/1,Ribbon拦截该请求 2.Ribbon通过EurekaServer拉取userservice 3.EurekaServer返回服务列表给Ribbon做负载均衡 …...

字符设备驱动实例(ADC驱动)
四、ADC驱动 ADC是将模拟信号转换为数字信号的转换器,在 Exynos4412 上有一个ADC,其主要的特性如下。 (1)量程为0~1.8V。 (2)精度有 10bit 和 12bit 可选。 (3)采样时钟最高为5MHz,转换速率最高为1MSPS (4)具有四路模拟输入,同一时…...

python基础5——正则、数据库操作
文章目录 一、数据库编程1.1 connect()函数1.2 命令参数1.3 常用语句 二、正则表达式2.1 匹配方式2.2 字符匹配2.3 数量匹配2.4 边界匹配2.5 分组匹配2.6 贪婪模式&非贪婪模式2.7 标志位 一、数据库编程 可以使用python脚本对数据库进行操作,比如获取数据库数据…...

SpringAOP原理:手写动态代理实现
0、基础知识 AOP我们知道,是在不修改源代码的情况下,为代码添加一些新功能的技术。通过动态代理,可以在不修改原始类代码的前提下,对方法进行拦截和增强。 动态代理常用于在不改变原有业务逻辑的情况下,对方法…...

【旅游度假】Axure酒店在线预订APP原型图 旅游度假子模块原型模板
作品概况 页面数量:共 10 页 兼容软件:Axure RP 9/10,不支持低版本 应用领域:旅游度假,生活服务 作品申明:页面内容仅用于功能演示,无实际功能 作品特色 本作品为「酒店在线预订」的移动端…...
Android JNI系列详解之CMake和ndk-build编译工具介绍
一、前提 CMake和ndk-build只是编译工具,本次主要介绍ndk-build和CMake的区别,下节课介绍他们的使用。 二、CMake工具介绍 CMake:cross platform make,是跨平台的编译工具 CMake是在AndroidStudio2.2之后引入(目前默认…...

【Linux取经路】解析环境变量,提升系统控制力
文章目录 一、进程优先级1.1 什么是优先级?1.2 为什么会有优先级?1.3 小结 二、Linux系统中的优先级2.1 查看进程优先级2.2 PRI and NI2.3 修改进程优先级2.4 进程优先级的实现原理2.5 一些名词解释 三、环境变量3.1 基本概念3.2 PATH:Linux系…...

TCP编程流程(补充)
目录 1、listen: 2、listen、tcp三次握手 3、 发送缓冲区和接收缓冲区: 4、tcp编程启用多线程 1、listen: 执行listen会创建一个监听队列 listen(sockfd,5) 2、listen、tcp三次握手 三次握手 3、 发送缓冲区和接收缓冲区:…...

每天一道leetcode:433. 最小基因变化(图论中等广度优先遍历)
今日份题目: 基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 A、C、G 和 T 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如,&quo…...

【C++】做一个飞机空战小游戏(十)——子弹击落炮弹、炮弹与飞机相撞
[导读]本系列博文内容链接如下: 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…...

去除UI切图边缘上多余的线条
最近接到UI切图,放进项目,显示边缘有多余线条,影响UI美观。开始以为切图没切好,实则不是。如图: ->解决: 将该图片资源WrapMode改为Clamp...

Spring高手之路13——BeanFactoryPostProcessor与BeanDefinitionRegistryPostProcessor解析
文章目录 1. BeanFactoryPostProcessor 概览1.1 解读 BeanFactoryPostProcessor1.2. 如何使用 BeanFactoryPostProcessor 2. BeanDefinitionRegistryPostProcessor 深入探究2.1 解读 BeanDefinitionRegistryPostProcessor2.2 BeanDefinitionRegistryPostProcessor 的执行时机2.…...
【LeetCode动态规划】详解买卖票I~IV,经典dp题型买
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…...

【深入探究人工智能】:常见机器学习算法总结
文章目录 1、前言1.1 机器学习算法的两步骤1.2 机器学习算法分类 2、逻辑回归算法2.1 逻辑函数2.2 逻辑回归可以用于多类分类2.3 逻辑回归中的系数 3、线性回归算法3.1 线性回归的假设3.2 确定线性回归模型的拟合优度3.3线性回归中的异常值处理 4、支持向量机(SVM&a…...
设计模式之解释器模式详解及实例
1、解释器设计模式概述: 解释器模式(Interpreter Pattern)是一种设计模式,它主要用于描述如何构建一个解释器以解释特定的语言或表达式。该模式定义了一个文法表示和解释器的类结构,用于解释符合该文法规则的语句。解…...

Nodejs沙箱逃逸--总结
一、沙箱逃逸概念 JavaScript和Nodejs之间有什么区别:JavaScript用在浏览器前端,后来将Chrome中的v8引擎单独拿出来为JavaScript单独开发了一个运行环境,因此JavaScript也可以作为一门后端语言,写在后端(服务端&#…...

No115.精选前端面试题,享受每天的挑战和学习
文章目录 变量提升和函数提升的顺序Event Loop封装 FetchAPI,要求超时报错的同时,取消执行的 promise(即不继续执行)强缓存和协商缓存的区别token可以放在cookie里吗? 变量提升和函数提升的顺序 在JavaScript中&#…...

Elasticsearch:语义搜索 - Semantic Search in python
当 OpenAI 于 2022 年 11 月发布 ChatGPT 时,引发了人们对人工智能和机器学习的新一波兴趣。 尽管必要的技术创新已经出现了近十年,而且基本原理的历史甚至更早,但这种巨大的转变引发了各种发展的“寒武纪大爆炸”,特别是在大型语…...

Flink学习笔记(一)
流处理 批处理应用于有界数据流的处理,流处理则应用于无界数据流的处理。 有界数据流:输入数据有明确的开始和结束。 无界数据流:输入数据没有明确的开始和结束,或者说数据是无限的,数据通常会随着时间变化而更新。 在…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...