学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学习笔记(一)
流处理 批处理应用于有界数据流的处理,流处理则应用于无界数据流的处理。 有界数据流:输入数据有明确的开始和结束。 无界数据流:输入数据没有明确的开始和结束,或者说数据是无限的,数据通常会随着时间变化而更新。 在…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...



















