程序环境和预处理(详解版)

我们已经学到这里,这就是关于C语言的最后一个集中的知识点了,虽然它比较抽象,但是了解这部分知识,可以让我们对C代码有更深层次的理解,知道代码在每一个阶段发生什么样的变化。让我们开始学习吧!


目录

1.程序的翻译环境和执行环境

2.编译+链接

2.1编译环境

2.2编译本身的几个阶段

2.3运行环境

3.预处理(预编译)

3.1预定义符号(这些符号都是语言内置的)

3.2#define和#undef

3.3命令行的定义

3.4条件编译

3.5文件包含

3.5.1头文件被包含的方式

3.5.2嵌套文件包含

4.其他预处理指令


1.程序的翻译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境

1.第一种是翻译环境,在这个环境中,源代码被转换为可执行的机器指令。

2.第二种是执行环境,它用于实际的代码执行

我们举个例子来理解它

例子:我们在2022VS中写了一段代码,我们是放在了.c文件中,他在执行时生成解决方案会产生一个.exe文件,这个exe文件就是这段代码的翻译环境

我们的.c文件要变为.exe文件要经过编译和链接,其中这个编译就是指我们的翻译环境

我们接下来详细学习编译和链接

2.编译+链接

2.1编译环境

1.组成一个程序的每个源文件通过编译过程分别转换成目标代码

2.每个目标文件由编译器捆绑在一起,形成一个单一而完整的可执行程序

3.链接器同时也会引入标准c函数库中任何被该程序用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中

每个源文件(.c)都对应一个目标文件(.exe)

我们用一个图谱来理解它

知道这个以后,我们来了解编译本身包括的几个阶段

2.2编译本身的几个阶段

(我们用gcc编辑器语法来说明,gcc比VS效果更明显)

我们首先画一个图供大家了解

我们已经 知道编译本身几个阶段就是这四个,我已经在图中标好序号,方便我们接下来的说明:

1.预处理(预编译):

命令:gcc -E test.c -o test.i

(其中:-E代表在预处理结束后代码停止执行

            -o代表指定生成的文件名是test.i)

执行的操作包括:#include头文件的包含

                             #define定义符号的替换和删除

                             注释的删除(文本删除)

2.编译:

命令:gcc -S test.i        生成了test.s文件

在这一步,它把C语言翻译成了汇编代码

执行的操作包括:语法分析

                             词法分析

                             语义分析

                             符号汇总(只有全局符号汇总,汇总的是全局变量)

3.汇编:

命令:gcc -c test.s   生成了test.o文件

                         在gcc中,这个test.o文件就是目标文件,存放的是二进制数据

执行的操作包括:把汇编代码翻译成了二进制指令(存放与test.o文件中)

                             行成符号表,生成对应表格(以编译的符号汇总为基础生成的)

例:假设我们有三个全局变量被符号汇总

4.链接:

在一个大型工程中,我们有多个test.c文件,这就代表我们要生成多个test.o的目标文件

执行的操作:符号表的合并和定位

在生成的符号表中查找跨文件的符号和数据

例如:

我们在这里创建两个.c文件,一个存放总体代码,一个存放函数功能的实现

2.3运行环境

也就是程序执行的过程:

1.程序必须载入内存中,在有操作系统的环境中,一般这个由操作系统完成,在独立环境中,程序的载入必须手工安排,也可能是通过可执行代码置入只读内存来完成

2.程序开始执行,接着便调用main函数

3.开始执行程序代码,这时函数程序将使用一个运行堆栈(stack),存储函数的局部变量和返回地址。程序同时可以使用静态内存(static)存储于静态,内存中的变量在程序的整个执行过程中一直保留它们的值

4.终止程序,正常终止main函数,也可能意外终止

3.预处理(预编译)

3.1预定义符号(这些符号都是语言内置的)

我们常见的预定义符号:

__FILE__            进行编译的源文件

__LINE__            当前文件的行号

__DATE__          文件被编译的日期

__TIME__           文件被编译的时间

__STDC__           如果编译遵循ANSI C,其值为1,否则未定义

__FUNCTION__    当前函数的名称

如果感兴趣,可以看一下它们的详细描述

我们看个代码例子:

//预定义符号

int main()
{
	printf("文件路径为:> %s\n", __FILE__);
	printf("文件的对应行号是:> %d\n", __LINE__);
	printf("文件被编译的日期是:> %s\n", __DATE__);
	printf("文件被编译的时间是:> %s\n", __TIME__);
	//printf("%s\n", __STDC__);VS2022中已经不能使用这个预定义符号了
	printf("当前执行函数的名称是:> %s\n", __FUNCTION__);
	return 0;
}

3.2#define和#undef

由于#define还是很有说法的,所以我们把这一对放在  详解#define  这一篇博客单独讲解,这样更好理解,在这里只要知道它们都是预定义符号,以及#define是只进行对应位置的替换的,是不计算的就好

3.3命令行的定义

许多C的编译器提供了一种能力(例如gcc),允许在命令行定义中定义符号,用于启动编译过程

当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处

例如我们在gcc环境下编译以下代码

int main()
{
	int arr[sz];//我们在这里不指定sz的大小,我们在命令行输入
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr[i] = i;
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

在gcc编译器命令行,我们输入

编译:gcc  test.c  -D  sz=10  -o  test.exe

其中 -D是指定sz的大小,在这里我们设置sz=10

        -o是指定生成的文件名是test.exe

运行:.\test.exe

然后我们发现显示了 0 1 2 3 4 5 6 7 8 9 这几个数字

sz的值的代码的替换也是在预处理工程中完成了,我们可以输入以下命令来查看细节

gcc  test.c  -D  sz=10  -E  -o  test.i

这里的-E是在预处理结束后代码停止

这时打开test.i文件中我们查看代码,我们会看到,代码sz的位置被替换为10

(这种方式就是命令行的方式,我们以后要学的linux操作系统这也这种编译方式)

3.4条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令

我们先了解一些常见的条件编译指令

1.#if   常量表达式

   ……

   #endif

2.多个分支的条件编译

#if

……

#elif

……

#else

……

#endif

3.判断是否被定义

#if defined(symbol),如果定义了symbol就执行下面的代码,否则就跳过

这条等价于#ifdef symbol

#if !defined (symbol),如果没有定义symbol,就执行下面的代码,否则就跳过

这条等价于#ifndef symbol

4.嵌套指令

#if  defined (os_UNIX)

#ifdef  OPTIONI

   unix  _version_  option1();

#endif

#ifdef  OPTION2

   unix  _version_  option2();

#endif

#elif defined(os_MSDOS)

#ifdef  OPTION2

   msdos _version_ option2();

#endif

#endif

接下来我们再来看个代码例子

#define PRINT 1
int main()
{
#ifdef PRINT//如果定义了PRINT,就打印hehe,否则不执行这段代码
	printf("hehe\n");
#endif
	return 0;
}

3.5文件包含

我们知道#include指令可以使另一个文件被编译,就像它实际出现于#include指令的地方一样

这种替换方式很简单:

预处理器先删除这条指令指令,并用包含文件的内容替换,这样一个源文件被包含10次,那就实际被编译了10次

我们介绍我们C语言中用到的文件包含:头文件包含和嵌套文件包含

3.5.1头文件被包含的方式

1.本地文件包含(就是指自己写出来的文件)

格式:#include“filename”

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件

2.库文件包含(我们平时用到的库函数,库里面有的文件)

格式:#include<filename.h>

查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误

这时我们可以得到一个结论,库函数也可以使用“ ”的形式包含,但我们不采用,因为它效率低

3.5.2嵌套文件包含

我们用图解来说明:

解决办法:条件编译

在每个头文件开头添加条件

1.方法一:#ifndef  _TEST_H_

                 #define _TEST_H_

                ……头文件的内容

                #endif

2.方法二:

在开头写 #pragma once

这两种方法都可以避免头文件的重复引入,被多次包含

4.其他预处理指令

这里我们只介绍几个,如果对这部分内容感兴趣的话,可以自己去了解

#error   编译程序时,只有遇到#error就会生成一个编译错误的提示信息,并停止编译

#pragma   可以设定编译程序完成的一些特定的动作,允许向编译程序传送各种指令

#line     改变当前行数和文件名称

#pragma pack() 修改默认对齐数(这个我们用过)


好,今天的学习就到这里,我们下期再见!!!

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

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

相关文章

5个免费在线工具推荐

NSDT 三维场景建模工具GLTF/GLB在线编辑器Three.js AI自动纹理化开发包YOLO 虚幻合成数据生成器3D模型在线转换 1、NSDT 三维场景建模 访问地址&#xff1a;NSDT 编辑器 2、GLTF/GLB在线编辑器 访问地址&#xff1a;GLTF 编辑器 3、Three.js AI自动纹理化开发包 图一为原始模…

C++类与对象(4)—日期类的实现

目录 一、类的创建和方法声明 二 、输出&运算符重载 三、检查合法性 1、获取对应年月的天数 2、初始化 四、实现加等和加操作 1、先写再写 2、先写再写 3、两种方式对比 五、实现自增和--自减 1、自增 2、自减 六、 实现减等和减操作 1、减等天数 2、加负数…

【数据结构/C++】线性表_双链表基本操作

#include <iostream> using namespace std; typedef int ElemType; // 3. 双链表 typedef struct DNode {ElemType data;struct DNode *prior, *next; } DNode, *DLinkList; // 初始化带头结点 bool InitDNodeList(DLinkList &L) {L (DNode *)malloc(sizeof(DNode))…

motionlayout的简单使用

MotionLayout 什么是motionLayout&#xff1f; MotionLayout 是 Android 中的一个强大工具&#xff0c;用于创建复杂的布局动画和过渡效果。它是 ConstraintLayout 的一个子类&#xff0c;继承了 ConstraintLayout 的布局功能&#xff0c;同时添加了动画和过渡的支持。Motion…

深度解析 Docker Registry:构建安全高效的私有镜像仓库

文章目录 什么是Docker Registry&#xff1f;Docker Hub vs. 私有RegistryDocker Hub&#xff1a;私有Registry&#xff1a; 如何构建私有Docker Registry&#xff1f;步骤一&#xff1a;安装Docker Registry步骤二&#xff1a;配置TLS&#xff08;可选&#xff09;步骤三&…

Adobe xd有免费版可以使用吗?

Adobexd现在收费了吗&#xff1f;Adobexd是收费的。Adobexd在中国提供个人版和团队版两项收费政策。个人版每月订阅60元&#xff0c;每年订阅688元&#xff1b;团队版每月订阅112元/用户&#xff0c;每年订阅1288元/用户。 虽然AdobeXD的免费计划已经下线&#xff0c;但Adobe仍…

穿山甲SDK 集成·android接入广告·app流量变现

接入穿山甲SDK的app 数独训练APP 广告接入示例: Android 个人开发者如何接入广告SDK&#xff0c;实现app流量变现 接入穿山甲SDK app示例&#xff1a; android 数独小游戏 经典数独休闲益智 随着移动互联网的快速发展&#xff0c;广告成为了许多应用开发者获取收益的主要方…

【2023.11.23】JDBC基本连接语法学习➹

1.导入jar包依赖&#xff1a;mysql-connector-java-8.0.27.jar 2.连接数据库&#xff01; 3.无法解析类->导入java.sql.*&#xff0c;&#xff08;将项目方言改为Mysql&#xff09; JDBC&#xff0c;启动&#xff01;&#xff01; public class Main {public static voi…

原生js实现form表单保存按钮触发后表单只读以及编辑按钮灰化,编辑按钮触发后表单是编辑状态以及保存按钮灰化

一、问题场景&#xff1f; 原生js实现form表单保存按钮触发后表单只读以及编辑按钮灰化&#xff0c;编辑按钮触发后表单是编辑状态以及保存按钮灰化 场景1&#xff1a;初始化进去的时候&#xff0c;编辑灰化&#xff0c;保存高亮&#xff0c;表单为编辑状态 场景2&#xff1a…

增量有余、后劲不足,星途汽车10月份销量环比下降3.9%

撰稿|行星 来源|贝多财经 近日&#xff0c;奇瑞集团发布了10月销量月报。报告显示&#xff0c;奇瑞集团于2023年10月销售汽车20.03万辆&#xff0c;同比增长50.8%&#xff0c;单月销量首次突破20万辆&#xff1b;2023年前10个月的累计销量为145.36辆&#xff0c;同比增长41.6…

利用STM32CubeMX解读时钟树

1&#xff0c;低速时钟 LSE是外部晶振作时钟源&#xff0c;主要提供给实时时钟模块&#xff0c;所以一般采用32.768KHz。LSI是由内部RC振荡器产生&#xff0c;也主要提供给实时时钟模块&#xff0c;频率大约为40KHz。(LSE和LSI)只是提供给芯片中的RTC(实时时钟)及IWDG(独立看门…

揭开未来:塑造数字营销的尖端技术

介绍 对于企业来说&#xff0c;要想在瞬息万变的数字营销世界中取得成功&#xff0c;领先一步至关重要。技术正在以惊人的速度发展&#xff0c;开辟了新的机会&#xff0c;正在改变营销人员与消费者的互动方式。这篇文章的目的是重点介绍重新定义数字营销领域的十大创新技术。…

如何进行MySQL的主从复制(MySQL5.7)

背景&#xff1a;在一些Web服务器开发中&#xff0c;系统用户在进行数据访问时&#xff0c;基本都是直接操作数据库MySQL进行访问&#xff0c;而这种情况下&#xff0c;若只有一台MySQL服务器&#xff0c;可能会存在如下问题 数据的读和写的所有压力都会由一台数据库独…

Vim 一下日志文件,Java 进程没了?

一次端口告警&#xff0c;发现 java 进程被异常杀掉&#xff0c;而根因竟然是因为在问题机器上 vim 查看了 nginx 日志。下面我将从时间维度详细回顾这次排查&#xff0c;希望读者在遇到相似问题时有些许启发。 时间线 15:19 收到端口异常 odin 告警。 状态:P1故障 名称:应用端…

c语言-数组长度的计算(结构体数组,字符串数组)

数组的长度我们可以直接根据函数声明得到数组的长度&#xff0c;或者可以通过计算的方法如下&#xff1a; 数组长度sizeof(数组名)/sizeof(数组类型) 测试代码如下&#xff1a;测试int整形数组&#xff0c;char字符数组&#xff0c;字符串数组&#xff0c;结构体数组。 #includ…

案例012:Java+SSM+uniapp基于微信小程序的科创微应用平台设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

VR直播如何打破视角壁垒,提升观看体验?

随着数字技术的不断发展&#xff0c;直播行业也发生了新的变革&#xff0c;VR直播也成为了直播行业中新的趋势&#xff0c;那么VR直播是如何打破视角壁垒&#xff0c;提升观看体验的呢&#xff1f; 杭州亚运会那几天&#xff0c;多项比赛热火朝天&#xff0c;无论你是参赛队伍的…

安全地公网访问树莓派等设备的服务 内网穿透--frp 23年11月方法

如果想要树莓派可以被公网访问&#xff0c;可以选择直接网上搜内网穿透提供商&#xff0c;一个月大概10块钱&#xff0c;也有免费的&#xff0c;但是免费的速度就不要希望很好了。 也可以选择接下来介绍的frp&#xff0c;这种方式不需要付费&#xff0c;但是需要你有一台有着公…

力扣283:移动零(JAVA)

题目描述: 意思是将所有0移到最后的同时其余非0元素位置仍然不变 如 1 2 0 5 2 0 经过移动零后变为 1 2 5 2 0 0 思路:使用双指针的思路来写 fast:从左往右遍历数组 slow:非零元素最后的一个位置 将数组分为3个区间 [0,slow]为处理好的非0数据,slow永远指向最后一个非0数据 [s…

vue3自定义拖拽指令

<template><div v-move class"box"></div> </template><script setup lang"ts"> import { Directive } from vue const vMove:Directive (el:HTMLElement) >{const mousedown (e:MouseEvent) >{// 鼠标按下const s…