程序环境和预处理

目录

一 程序的翻译环境和执行环境

二 详解编译+链接

2.1 翻译环境

2.2 编译本身也分为几个阶段

2.3 运行环境

三 预处理详解

3.1 预定义符号

3.2 #define

3.2.1 #define 定义标识符

3.2.2 #define定义宏

3.2.3 #define 替换规则

3.2.4 #和##

3.2.5 带副作用的宏参数

3.2.6 宏和函数对比

3.2.7 命名约定

3.3 #undef

3.4 命令行定义

3.5 条件编译

3.6 文件包含

3.6.1 头文件被包含的方式

3.6.2 嵌套文件包含


励志小模块

不要太在意过去,下次做好就行。


重点:程序的翻译环境、程序的执行环境、详解:C语言程序的编译+链接、预定义符号介绍 、预处理指令 #define 、宏和函数的对比 、预处理操作符###的介绍 、命令定义 、预处理指令 #include 、预处理指令 #undef 、条件编译


一 程序的翻译环境和执行环境

不同的编译器,对于缓冲区实现的方式不同。

ANSI C的任何一种实现中,存在两个不同的环境: 1 种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。

二 详解编译+链接

2.1 翻译环境

翻译环境分成两个部分:编译和链接;(C语言源代码通过编译生成目标文件(.obj/.o)(多个源文件单独通过编译器生成各自的目标文件),目标文件加上链接库通过链接器 链接成为可执行程序(.exe))

 编译过程:预编译、编译、汇编

2.2 编译本身也分为几个阶段

预编译(预处理)——编译——汇编  (预编译也叫作预处理)

      预处理之后产生的结果都放在test.i文件中,编译完成之后产生的结果保存在test.s中,汇编完成之后产生的结果保存在test.o或者是test.obj(在linux中是.o;Windows中是.obj)

预编译:(1)进行头文件的展开(2)删除注释(3)#define定义的符号替换(例如:#define Max 100,在预编译结束之后,Max会被替换为100,并把#define这一行给删掉)
总而言之,就是进行一些文本操作。

编译(把C语言代码转换成汇编代码): (1)语法分析(2)词法分析(3)语义分析(4)符号汇总(全局符号:例如:函数名、main等)

汇编(把汇编代码转换成二进制的指令):(1)形成符号表(全局符号给一个地址,所有全局符号+地址形成符号表)

 目标文件(.o)加上链接库通过链接器 链接成为可执行程序(.exe))

linux中.o目标文件以及可执行文件的文件格式是elf

链接(编译之后生成.o文件,加上链接库通过链接器进行链接):(1)合并段表(2)符号表(不同文件的符号表)的合并以及重定义

链接的时候,多个目标文件进行链接的时候会通过符号表,查看来自外部的符号是否真实存在

2.3 运行环境

程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用 main 函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack ),存储函数的局部变量和返回 地址。程序同时也可以使用静态( static )内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止 main 函数;也有可能是意外终止。

三 预处理详解

这里讲述的内容都是在预处理阶段

3.1 预定义符号

__FILE__       // 进行编译的源文件
__LINE__     // 文件当前的行号
__DATE__     // 文件被编译的日期
__TIME__     // 文件被编译的时间
__STDC__     // 如果编译器遵循 ANSI C ,其值为 1 ,否则未定义 (这个并不是所有的编译器都支持)

例子:

#include <stdio.h>
int main()
{
	printf("%s\n", __FILE__);//编译出来的结果,会显示运行文件的路径
	printf("%d\n", __LINE__);//编译出来的结果,显示该行的行数303
	printf("%s\n", __DATE__);//显示时间
	printf("%s\n", __TIME__);//显示日期
	return 0;
}

记录杂志:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
	int i = 0;
	FILE* pf = fopen("log.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	for (i = 0; i < 10; i++)
	{
		fprintf(pf, "%s %s %s %d %d\n", __DATE__, __TIME__, __FILE__, __LINE__, i);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

3.2 #define

3.2.1 #define 定义标识符

语法: #define name stuff  (注意:定义完之后,最后不需要分号(;))
#define MAX 1000
#define reg register               // register 这个关键字,创建一个简短的名字
#define do_forever for(;;)     // 用更形象的符号来替换一种实现
#define CASE break;case   // 在写 case 语句的时候自动把 break 写上。
// 如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠 ( 续行符 )
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                                               date:%s\ttime:%s\n" ,\
                                               __FILE__,__LINE__ ,       \
                                                __DATE__,__TIME__ )

3.2.2 #define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏( macro )或定义宏(define macro )。
#define name( parament - list ) stuff 
其中的 parament - list 是一个由逗号隔开的符号,它们可能出现在 stuff 中。
注意: 参数列表的左括号必须与name 紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
#include <stdio.h>
#define SQUARE(x) x * x     
 //这也是个替换,用后面的内容替换前面的宏

int main()
{
	int a = 5;
	printf("%d\n", SQUARE(a + 1));//打印结果为11
	return 0;
}

解析:打印结果,我们会理所应当的以为是36,但是结果却是11,因为代入后是a + 1* a+1,所以结果是11,要是想让结果为36,正确的应该是#define SQUARE(x)  ((x) * (x))

注意:定义宏,一定要注意括号,记得带上(大部分情况是需要带的,否则会出现优先级问题,导致想要的结果和预期的不一样)

比如上述代码,我们想要的是36,但是结果却是11

每一个部分带上括号,整体也要带上括号

3.2.3 #define 替换规则

先替换,后计算

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索(意思就是printf("M = %d", 10)中的M就不会被替换)。

3.2.4 #和##

使用 # 把一个宏参数变成对应的字符串
#include <stdio.h>
#define PRINT(n) printf("the value of " #n " is %d\n", n)
//#n相当于字符串("a","b"),插入到这个位置,三个都是字符串,//不加引号(;)

int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	printf("the value of " "b" " is %d\n", b);//the value of 是一个字符串,b是一个字符串,is %d\n 是一个字符串
	return 0;
}

 注意:字符串是有自动连接的特点

## 可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

 

#include <stdio.h>
#define CAT(Class, num) Class##num

int main()
{
	int Class1 = 100;
	printf("%d\n", CAT(Class, 1));//Class1
	return 0;
}

打印结果:100

3.2.5 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
#include <stdio.h>

int main()
{
	int a = 2;
	int b = 0;
	b = ++a;//a = 3; b = 3//这个就有副作用
	a = 2;
	b = a + 1;//a = 2; b = 3 这个没有副作用
	return 0;
}
#include <stdio.h>
#define MAX(x, y) ((x) > (y)? (x): (y))
int main()
{
	int a = 3;
	int b = 5;
	int m = 0;
	m = MAX(a++, b++);//((a++) > (b++) ? (a++) : (b++))后置++,先使用后++ 3>5 a=4 b=6  m=5    b=7
	printf("%d\n", m);
	printf("a = %d, b = %d", a, b);
	return 0;
}

m=6 a=4 b=7

注意是替换

3.2.6 宏和函数对比

宏通常被应用于执行简单的运算
1.用于调用函数和从函数返回的代码可能比实际执行小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹
2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之   宏是类型无关的
缺点: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。2. 宏是没法调试的。(宏是替换)(把代码插入到相应的位置)3.宏由于类型无关,也就不够严谨4.宏可能会带来运算符优先级的问题,导致程容易出现错误。
宏的参数可以出现类型,但是函数不可以
#include <stdio.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	int* p2 = MALLOC(10, int);
//p1 p2效果一样
	return 0;
}

3.2.7 命名约定

习惯:把宏名全部大写 函数名不要全部大写

3.3 #undef

这条指令用于移除一个宏定义。
#undef NAME
// 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
#include <stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))

int main()
{
#undef MAX
	int m = 0;
	m = MAX(2, 3);//此时,这个代码无法用MAX这个宏
	printf("%d\n", m);
	return 0;
}

3.4 命令行定义

许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 编译时进行赋值,代码中并没有进行赋值
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)

3.5 条件编译

条件编译指令:在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃编译

比如:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。  

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
#if 0
//1的时候就可以运行,不能是变量
		printf("%d\n", i);//此时,这个代码就不运行了
#endif
//这是预处理命令
		//其他
	}
	return 0;
}
常见的条件编译指令:
1.
#if 常量表达式
//...
#endif
// 常量表达式由预处理器求值。
2. 多个分支的条件编译
#if 常量表达式
      //...
#elif 常量表达式
      //...
#elif 常量表达式
      //...
#else
     //...
#endif
3. 判断是否被定义
(1)定义
#if defined(symbol)
      //...
#endif
例如:#define M 100;symbol就是指M
(2)定义
#ifdef symbol
      //...
#endif
(3)没有定义
#if !defined(symbol)
      //...
#endif
(4)没有定义
#ifndef symbol
      //...
#endif
4. 嵌套指令
#if defined(OS_UNIX)
      #ifdef OPTION1
          unix_version_option1 ();
      #endif
      #ifdef OPTION2
           unix_version_option2 ();
      #endif
#elif defined(OS_MSDOS)
      #ifdef OPTION2
              msdos_version_option2 ();
      #endif
#endif

3.6 文件包含

#include 指令可以使另外一个文件被编译。
预处理器先删除这条指令,并用包含文件的内容替换。 如果一个源文件被包含10 次,那就实际被编译 10 次。

3.6.1 头文件被包含的方式

(1)本地文件包含 #include "filename"   
  先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
准位置查找头文件。
(2)库文件包含 #include <filename.h>
  查找头文件直接去标准路径下去查找

库文件也可以用""的形式包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

3.6.2 嵌套文件包含

如果文件被多次包含,这样就会造成文件内容的重复

解决办法:

(1)

#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif   //__TEST_H__ 根据文件名改变

(2)

#pragma once

 以上两种办法可以避免头文件的重复引入

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17138.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

告别被拒,如何提升iOS审核通过率(上篇)

iOS审核一直是每款移动产品上架苹果商店时面对的一座大山&#xff0c;每次提审都像是一次漫长而又悲壮的旅行&#xff0c;经常被苹果拒之门外&#xff0c;无比煎熬。那么问题来了&#xff0c;我们有没有什么办法准确把握苹果审核准则&#xff0c;从而提升审核的通过率呢&#x…

Centos7快速安装Kibana并连接ES使用

Elasticsearch 提供了一个名为 Kibana 的官方可视化界面。Kibana 是一个开源的数据可视化和管理工具&#xff0c;用于 Elasticsearch。它提供了丰富的功能&#xff0c;如仪表板、图表、地图等&#xff0c;帮助您更好地理解、搜索和可视化存储在 Elasticsearch 中的数据。 在 C…

【软考备战·希赛网每日一练】2023年5月5日

文章目录 一、今日成绩二、错题总结第一题 三、知识查缺 题目及解析来源&#xff1a;2023年05月05日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; 有返回消息的就是同步消息&#xff1b;不需要等待返回消息就可以去做其他事情的请求消息就是异步消息…

从零基础到网络安全专家:全网最全的网络安全学习路线

前言 网络安全知识体系非常广泛&#xff0c;涉及的领域也非常复杂&#xff0c;有时候即使有想法和热情&#xff0c;也不知道从何入手。 为了帮助那些想要进入网络安全行业的小伙伴们更快、更系统地学习网络安全知识&#xff0c;我制定了这份学习路线。本路线覆盖了网络安全的…

网络协议与攻击模拟-03-ARP协议

ARP 协议&#xff08;地址解析协议&#xff09; 一、 ARP 协议 将一个已知的 IP 地址解析为 MAC 地址&#xff0c;从而进行二层数据交互 是一个三层的协议&#xff0c;但是工作在二层&#xff0c;是一个2.5层协议 二、工作流程 1、两个阶段 ARP 请求 ARP 相应 2、 ARP 协议…

Java 基础入门篇(三)—— 数组的定义与内存分配

文章目录 一、数组的定义1.1 静态初始化数组1.2 动态初始化数组1.3 数组的访问 二、数组的遍历三、数组的内存图 ★3.1 Java 的内存分配3.2 数组的内存图3.3 两个数组变量指向同一个数组对象 四、数组使用的常见问题补充&#xff1a;随机数 Random 类 一、数组的定义 数组就是…

黑盒测试过程中【测试方法】详解2-正交实验

在黑盒测试过程中&#xff0c;有9种常用的方法&#xff1a;1.等价类划分 2.边界值分析 3.判定表法 4.正交实验法 5.流程图分析 6.因果图法 7.输入域覆盖法 8.输出域覆盖法 9.猜错法 前面我们已经讲解过了等价类划分、边界值、判定表。 可以参考我之前的文章&#xff…

MySQL 常用命令

#--------------------------- #----cmd命令行连接MySql--------- cd C:\Program Files\MySQL\MySQL Server 5.5\bin # 启动mysql服务器 net start mysql # 关闭mysql服务器 net stop mysql # 进入mysql命令行 mysql -h localhost -u root -p 或mysql -u root -p #---------…

SPSS如何进行回归分析之案例实训?

文章目录 0.引言1.线性回归分析2.曲线回归分析3.非线性回归分析4.Logistic回归分析5.有序回归分析6.概率回归分析7.加权回归分析 0.引言 因科研等多场景需要进行数据统计分析&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合…

璞华助力“数字人社”,为成都市人社数字化建设提供多方位的产品与技术支持!

新的时期&#xff0c;人力资源和社会保障事业进入新一轮的制度创新和加快发展阶段。把对各项人力资源和社会保障业务的支持和服务纳入信息化建设&#xff0c;通过 “数字人社”信息化建设项目&#xff0c;是充分利用新一代信息技术&#xff0c;有效整合各类信息资源&#xff0c…

为什么说网络安全行业是IT行业最后的红利?

前言 2023年网络安全行业的前景看起来非常乐观。根据当前的趋势和发展&#xff0c;一些趋势和发展可能对2023年网络安全行业产生影响&#xff1a; 5G技术的广泛应用&#xff1a;5G技术的普及将会使互联网的速度更快&#xff0c;同时也将带来更多的网络威胁和安全挑战。网络安全…

石头科技2022年营收实现双位数增长,以技术实力打响创新价值战

近日&#xff0c;石头科技披露了2022年度财务报告&#xff0c;报告显示&#xff0c;在在较大内外部压力下&#xff0c;石头科技2022年营收依然实现双位数增长&#xff0c;且境内外销售收入平稳增长。 该公司在近年来不断完善其产品矩阵&#xff0c;目前已推出手持无线吸尘、商…

想回西安

五一假期结束了&#xff0c;开始营业总结下跟读者们的交流。 特别感谢大家让我在自己的技术号里面写一些和生活相关的事情&#xff0c;现在正常营业&#xff0c;回复下读者的问题。 问题 发哥&#xff0c;我现在有个疑惑能麻烦帮我解答下嘛。 我已经工作一年多了&#xff0c;但…

Java基础(二十一):集合源码

Java基础系列文章 Java基础(一)&#xff1a;语言概述 Java基础(二)&#xff1a;原码、反码、补码及进制之间的运算 Java基础(三)&#xff1a;数据类型与进制 Java基础(四)&#xff1a;逻辑运算符和位运算符 Java基础(五)&#xff1a;流程控制语句 Java基础(六)&#xff1…

第十九章 观察者模式

文章目录 前言普通方式解决问题CurrentConditions 显示当前天气情况WeatherData 管理第三方Clint 测试 一、观察者模式(Observer)原理完整代码SubjectObserverWeatherData implements SubjectCurrentConditions implements ObserverBaiduSite implements ObserverClint 前言 普…

python人工智能【隔空手势控制鼠标】“解放双手“

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

SSL证书支持IP改成https地址

我们都知道SSL证书能为域名加密&#xff0c;那么IP地址可以实现https加密吗&#xff1f;答案当然是肯定的。为IP地址进行https加密不仅能保护IP服务器与客户端之间数据传输安全&#xff0c;还能对IP服务器进行身份验证&#xff0c;确保用户信息安全&#xff0c;增强用户对IP地址…

编译一个魔兽世界开源服务端Windows需要安装什么环境

编译一个魔兽世界开源服务端Windows需要安装什么环境 大家好我是艾西&#xff0c;去年十月份左右wy和bx发布了在停服的公告。当时不少小伙伴都在担心如果停服了怎么办&#xff0c;魔兽这游戏伴随着我们渡过了太多的时光。但已经发生的事情我们只能顺其自然的等待GF的消息就好了…

平均情况时间复杂度

// n表示数组array的长度 int find(int[] array, int n, int x) {int i 0;int pos -1;for (; i < n; i) {if (array[i] x){ pos i; break;}}return pos; } 通过以上代码&#xff0c;我们分析一下平均情况时间复杂度。 以上代码要查找的变量 x 在数组中的位置&#xff…

2023哪款蓝牙耳机性价比高?200左右高性价比蓝牙耳机推荐

现如今的蓝牙耳机越来越多&#xff0c;人们在选择时不免纠结&#xff0c;不知道选什么蓝牙耳机比较好&#xff1f;针对这个问题&#xff0c;我来给大家推荐几款性价比高的蓝牙耳机&#xff0c;一起来看看吧。 一、南卡小音舱Lite2蓝牙耳机 参考价&#xff1a;299 蓝牙版本&am…