C语言进阶(37) | 程序环境和预处理
目录
1.程序的翻译环境和执行环境
2.详解编译+链接
2.1 翻译环境
2.2 编译本身也分为几个阶段:
2.3 运行环境
3.预处理详解
3.1预定符号
3.2 #define
3.3 #undef
3.4 命令行定义
3.5 条件编译
3.6 文件包含
了解重点:
- 程序的翻译环境
- 程序的执行环境
- 详解: C语言程序的编译+链接
- 预定义符号介绍
- 预处理指令#define
- 宏和函数的对比
- 预处理操作符#和##的介绍
- 命令定义预处理指令#include
- 预处理指令#undef
- 条件编译
1.程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境
- 第1种是翻译环境,在这个环境中,代码被转换为可执行的机器指令。
- 第2种是执行环境,它用于实际执行代码。
2.详解编译+链接
2.1 翻译环境
- 组成一个程序的每一个源文件通过编译过程分别转换成目标文件。
- 每个目标文件有链接器捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
2.2 编译本身也分为几个阶段:
编译 | 链接 | ||
预编译阶段 | 编译 | 汇编 | 链接 |
1.头文件#include 2.define定义符号、宏替换 | 语法分析 语义分析 符号汇总 | 将汇编代码转 换成二进制指令 | 1.合并段表 2.符号表的合并和 符号表的重定位 |
2.3 运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候学将甫注环运待的中k),存储函数的局部变量和返回地址。程序同时也可以使用静态 (static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
3.预处理详解
3.1预定符号
- __FILE__ //进行编译的源文件
- __LINE__ //文件当前的行号
- __DATE__ //文件被编译的日期
- __TIME__ //文件被编译的时间
- __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的
举个例子:
#include<stdio.h>
int main()
{printf("%s\n",__FILE__);printf("%d\n",__LINE__);printf("%s\n",__DATE__);printf("%s\n",__TIME__);return 0;
}
3.2 #define
3.2.1 #define 定义标识符
#define M 100
int main()
{int m = M;printf("%d\n",m);return 0;
}
3.2.2 #define 定义宏
- #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏 (macro) 或定义宏(define macro)。
- 下面是宏的申明方式
#define name( parament-list ) stuff
其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。 - 注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3));return 0;
}
再举例:乍一看,你可能觉得这段代码将打印16这个值
却打印出来7,为什么呢?
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3+1)); //这里不会先计算3+1,而是直接替换printf("%d\n",3+1*3+1);//所以输出7return 0;
}
所以写成以下代码:
#define SQUARE(x) ((x)*(x))//这里不会先计算3+1,因为添加了括号
int main()
{printf("%d\n",SQUARE(3+1)); printf("%d\n",(3+1)*(3+1));//所以输出16return 0;
}
3.2.3 #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
3.2.4 #和##
如何把参数插入到字符串中?
首先我们看看这样的代码:
写一个宏:要求:
当输入a时候输出:the value of a is 10
当输入b时候输出the value of b is 20
当输入c时候输出 the value of c is 30
#define PRINT(x) printf("the value of "#x" is %d\n",x);
int main()
{int a = 10;PRINT(a);int b = 20;PRINT(b);int c = 30;PRINT(c);return 0;
}
再完整一点
#define PRINT(x,format) printf("the value of "#x" is "format"\n",x);
int main()
{int a = 10;PRINT(a,"%d");int b = 20;PRINT(b,"%d");float c = 5.5f;PRINT(c,"%f");return 0;
}
##用来拼接字符
#define CAT(x,y) x##y
int main()
{int class101 = 300;printf("%d\n",CAT(class,101));//这里相当于printf("%d\n",class101);return 0;
}
3.2.5 带副作用的宏参数
- 当宏参数在宏的定义中出现超过一次的时候,如果参数带者作用那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
x+1; //不带副作用
x++; //带有副作用
例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5;int b = 8;int m = MAX(a++,b++);printf("%d %d\n",a,b); //输出6 10//在宏中发生了变化return 0;
}
3.2.6 宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中越出较大的一个。
#define MAX(x,y) ((x)>(y)?(x):(y))
那为什么不用函数来完成这个任务?
【原因有二:】
1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
【当然和宏相比函数也有劣势的地方:】
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没法调试的。
3.宏由于类型无关,也就不够严谨。
4.宏可能会带来运算符优先级的问题,导致程容易出现错 。
- 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到.
【宏和函数额对比】
属性 | #define定义宏 | 函数 |
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
【命名约定】
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者那我们平时的一个习惯是:
- 把宏名全部大写
- 函数名不要全部大写
3.3 #undef
这条指令用于移除一个宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
3.4 命令行定义
- 许多C的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
- 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)
3.5 条件编译
- 在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
- 比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译
如下:没有定义PRINT,就不会打印hehe
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
如下:定义过了PRINT,就会打印hehe
#define PRINT
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
3.6 文件包含
3.6.1 文件被包含式:
- 我们已经知道,#include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。
- 这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次 - 本地文件包含:
#include"filename"
查找策略: 先在源文件所在目录下查找,
如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。 - 库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误
这样是不是可以说,对于库文件也可以使用“”的形式包含?答案是肯定的,可以。 - Linux环境的标准头文件的路径:
/usr/incIude - VS环境的标准头文件的路径:
C:\Program Files (x86)\Microsoft Visual studio 12.0 \VC\include
3.6.2 套文件包含
- 如何解决这个问题?
答案:条件编译。
每个头文件的开头写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__ - 或者:
#pragma once
就可以避免头文件的重复引入。
C语言初识(1) | C简介 | 主函数 | 数据类型
C语言初识(2) | 变量和常量
C语言初识(3) | 字符串 | 转译字符
C语言初识(4) | 顺序 | 选择 | 循环
C语言初识(5) | 函数 | 数组 | 操作符
C语言初识(6) | 关键字
C语言初识(7) | 指针| 结构体 | define
C语言初阶(8) | 选择结构 | if_else | switch
C语言初阶(9) | while | break | continue | getchar
C语言初阶(10) | 初识for循环 | 入门
相关文章:
C语言进阶(37) | 程序环境和预处理
目录 1.程序的翻译环境和执行环境 2.详解编译链接 2.1 翻译环境 2.2 编译本身也分为几个阶段: 2.3 运行环境 3.预处理详解 3.1预定符号 3.2 #define 3.3 #undef 3.4 命令行定义 3.5 条件编译 3.6 文件包含 了解重点: 程序的翻译环境程序的执行环境详解: C语言程…...
Golang每日一练(leetDay0005)
目录 13. 罗马数字转整数 Roman to Integer ★ 14. 最长公共前缀 Longest Common Prefix ★ 15. 三数之和 3Sum ★★★ 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 13. 罗马数字转…...
occt_modeling_data(一)——拓扑
下面是我基于opencascade英文文档中关于occt_modeling_data中Topology部分进行的翻译,英文好的还是建议直接看文档,部分我不肯定的地方我会附上英文原句。如发现有错误欢迎评论区留言。 OCCT Topolog允许用户访问和操纵物体的数据,且不需要处…...
【AcWing】蓝桥杯备赛-深度优先搜索-dfs(3)
目录 写在前面: 题目:93. 递归实现组合型枚举 - AcWing题库 读题: 输入格式: 输出格式: 数据范围: 输入样例: 输出样例: 解题思路: 代码: AC &…...
宇宙最强-GPT-4 横空出世:最先进、更安全、更有用
文章目录前言一、准确性提升1.创造力2.视觉输入3.更长的上下文二、相比于ChatGPT有哪些提升1.GPT-4 的高级推理能力超越了 ChatGPT2.GPT-4 在多种测试考试中均优于 ChatGPT。三、研究团队在GPT-4模型都做了哪些改善1.遵循 GPT、GPT-2 和 GPT-3 的研究路径2.我们花了 6 个月的时…...
HashMap的实际开发使用
目 录 前言 一、HashMap是什么? 二、使用步骤 1.解析一下它实现的原理 编辑 2.实际开发使用 总结 前言 本章,只是大概记录一下hashMap的简单使用方法,以及理清一下hashMap的put方法的原理,以及get方法的原理。 一、Has…...
OpenCV入门(十三)快速学会OpenCV 12 图像梯度
OpenCV入门(十三)快速学会OpenCV 12 图像梯度1.Sobel算子1.1 计算x1.2 计算y1.3 计算xy2.Scharr算子2.1 计算x2.2 计算y2.3 计算xy3.Laplacian算子4.总结图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯…...
软考:常见小题目计算题
01采购合同的类型采购合同主要包括总价类合同、成本补偿类合同、工料合同三大类合同。1、总价类合同此类合同为既定产品、服务或成果的采购设定一个总价。这种合同应在已明确定义需求,且不会出现重大范围变更的情况下使用。包括:(1࿰…...
【Linux】进程的程序替换
文章目录1. 程序替换1.创建子进程的目的是什么?2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候,先有进程数据结构,还是先加载代码和数据?程序替换是整体替换,不是局部替换execl 返回值4. 替换函数1…...
【C++】模板(上)
文章目录1、泛型编程2、函数模板函数模板的实例化模板参数的匹配原则3、 类模板类模板的实例化1、泛型编程 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left …...
express框架利用formidable上传图片
express框架,在上传图片功能方面,用formidable里面的incomingform功能,很方便。很多功能都已经封装好了,非常好用,简单,不需要写更深层次的代码了。确实不错。 下面是我自己跟着黑马教程的博客系统的部分&…...
测试背锅侠?入职软件测试后大d佬给我丢了这个bug分类分析,至今受益匪浅......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 刚成为入职…...
STM32 OTA应用开发——通过内置DFU实现USB升级(方式1)
STM32 OTA应用开发——通过内置DFU实现USB升级(方式1) 目录STM32 OTA应用开发——通过内置DFU实现USB升级(方式1)前言1 硬件介绍2 环境搭建2.1 Keil uVsion2.2 zadig2.3 STM32CubeProgrammer2.4 安装USB驱动3 OTA升级结束语前言 …...
基于MFC的JavaScript进行网页数据交互
目录 前言 一、创建html对话框工程 二、使用步骤 1.引入JavaScript接口代码 2.重写相关接口 3.在html网页中添加C/C调用的接口 4.在MFC工程中添加调用接口 5.设置确认按键触发调用 6.运行结果 总结 前言 如何快速的进行MFC开发,这里我介绍一种JavaScript与C/C交互的…...
AUTOSAR-Fee
Fee模块 全称Flash EEPROM Emulation Module,属于ECU抽象层 Fee模块本身是脱离硬件的,但是Fee模块可能会引用的Fls模块定制API,所以只能算半抽象. FEE模块应从设备特定的寻址方案和分段中抽象出来,并为上层提供虚拟寻址方案和分段(virtual addressing scheme and segment…...
Linux基本命令——操作演示
Linux基本命令——操作演示Linux的目录结构Linux命令入门目录切换相关命令(cd/pwd)相对路径、绝对路径和特殊路径符创建目录命令(mkdir)文件操作命令part1 (touch、cat、more)文件操作命令part2 (cp、mv、rm)查找命令 …...
【Linux】目录和文件的权限
Linux中的权限有什么作用Linux权限管理文件访问者的分类文件类型和访问权限(事物属性)**文件权限值的表示方法**文件访问权限的相关设置方法chmodchownchgrpumaskumask使用 sudo分配权限目录的权限Linux中的权限有什么作用 Linux下有两种用户࿱…...
Unity 优化之Player Setting
Quality SettingPixel Light Count 使用前向渲染时最大像素光源数。也是性能关键。数量越大消耗越多。Texture Quality:贴图质量,可以选择Half Res,这样速度会更快,但是贴图质量会轻微下降。Anisotropic Textures 纹理各向异形Ant…...
Qt——通过一个简单的程序例程熟悉使用Qt Creator软件进行项目搭建的基本流程(新建项目、项目的文件组成、修改ui文件、编译运行与调试)
【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》 《QT开发实战》 《Android开发实战》...
Linux 如何使用 git | 新建仓库 | git 三板斧
文章目录 专栏导读 一、如何安装 git 二、注册码云账号 三、新建仓库 配置仓库信息 四、克隆远端仓库到本地 五、git 三板斧 1. 三板斧第一招:git add 2. 三板斧第二招:git commit 解决首次 git commit 失败的问题 配置机器信息 3. 三…...
3.springcloud微服务架构搭建 之 《springboot自动装配ribbon》
1.springcloud微服务架构搭建 之 《springboot自动装配Redis》 2.springcloud微服务架构搭建 之 《springboot集成nacos注册中心》 ribbon工作原理自己网上百度,说的都很详细 目录 1.项目引入openfeign和ribbon配置 2.新建lilock-ribbon-spring-boot-starter 3…...
【一】进程到底是个啥?
1. 什么是进程 进程(process):一个运行起来的程序,就是进程!,我们可以在任务管理中看到进程。 进程是操作系统进行资源分配的基本单位 2. 进程的管理 所谓的进程管理,其实就是分为两步&…...
[蓝桥杯] 双指针、BFS和DFS与图论问题
文章目录 一、日志统计 1、1 题目描述 1、2 题解关键思路与解答 二、献给阿尔吉侬的花束 2、1 题目描述 2、2 题解关键思路与解答 三、红与黑 3、1 题目描述 3、2 题解关键思路与解答 3、2、1 dfs题解代码 3、2、2 bfs题解答案 四、交换瓶子 4、1 题目描述 4、2 题解关键思路与…...
编译原理陈火旺版第四章课后题答案
下面答案仅供参考! 1.考虑下面文法G1: (1) 消去 Q 的左递归。然后,对每个非终结符,写岀不带回溯的递归子程序。 (2) 经改写后的文法是否是LL(1)的?给出它的预测分析表。 2.对下面的文法G: P→(E)lalblΛ (1)计算这个文法的每个非…...
【LeetCode】剑指 Offer(25)
目录 题目:剑指 Offer 49. 丑数 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 49. 丑数 - 力扣&…...
【数据结构】链表OJ
Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 编辑 编辑二、分享:OJ调试技巧 编辑三、链表的中间结点 编辑四、链表中倒数第k个结点 一、移除链表元素 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,…...
电子工程师必须掌握的硬件测试仪器,你确定你都掌握了?
目录示波器示例1:测量示波器自带的标准方波信号输出表笔认识屏幕刻度认识波形上下/左右移动上下/左右刻度参数调整通道1的功能界面捕获信号设置Menu菜单触发方式触发电平Cursor按钮捕捉波形HLEP按钮参考资料频谱分析仪器信号发生器示波器 示例1:测量示波…...
高速PCB设计指南系列(四)
第二篇 抗干扰3(部分) 3 提高敏感器件的抗干扰性能 提高敏感器件的抗干扰性能是指从敏感器件这边考虑尽量减少对干扰噪声 的拾取,以及从不正常状态尽快恢复的方法。 提高敏感器件抗干扰性能的常用措施如下: (1&…...
ODrive入门配置
目录一、驱动板说明二、安装python三、安装odrivetool四、接线五、zadig设置SimpleFOC、ODrive和VESC教程链接汇总:请点击一、驱动板说明 ODrive 硬件版本:V3.6-56V, 工作电压:12V-56V, 工作电流:60A ODri…...
快速测试两台服务器间的网速(ChatGPT回复)
如何使用iperf3测试从远程服务器下载文件速度 在进行网络性能测试时,了解服务器之间的带宽和延迟是非常重要的。iperf3是一种用于测量网络性能的工具,可以帮助我们测试从远程服务器下载文件的速度。本文将介绍如何在本地计算机上使用iperf3测试从远程服…...
泸州市规划建设局网站/seo入门
angular和reactNilson Jacques , Chris Perry和Thomas Greco对本文进行了同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态! 在SitePoint的论坛上,我偶然发现了一个名为So Many Frameworks的线程,其中Guido…...
凡客网站网址/国际大新闻最新消息
php获取url中的参数信息,是PHP面试过程中常见的考点之一,也是我们日常项目开发过程中,经常需要实现的一个功能,我们可以通过PHP中parse_url等相关函数来实现。那么对于PHP新手来说,具体怎么实现可能有一定的难度&#…...
温州做网站就来温州易富网络/弹窗广告最多的网站
父窗体Form1 子窗体Form2 Form1中有一个datagridview控件和一添加按钮,Form2中有一个Text控件和一个保存按钮 要求点击Form1窗体上的添加按钮,弹出Form2,再text里面输入内容,点击保存自动关闭Form2,刷新Form1中datagri…...
岳阳整站优化/关键词搜索推广
UINavigationController详解 通过这个接口可以初始化自定义的工具栏和导航栏 - (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass NS_AVAILABLE_IOS(5_0) 使用水平滑动过渡。如果视图控制器已在堆栈中在调用就没有效果 -…...
织梦做的网站如何放在网上/武汉百度搜索优化
每个寄存器在理论上都可以从RAM读取信息或将信息写入RAM中,ALU算数逻辑单元,它很容易4个字节上进行加法减法移位操作。...
电子政务门户网站建设的教训/怎么注册一个自己的网址
paip. 混合编程的实现resin4 (自带Quercus ) 配置 php 环境#---混合编程的类型1.代码inline 方式2.使用库/api 解析方式.#----配置resin 支持phpresin4默认自动支持php..也能手动配置了.web.xml加php的servlet解析..参考Quercus让你的PHP开心在Servlet容器奔跑#---…...