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

news2024/9/22 4:15:21

我们已经学到这里,这就是关于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() 修改默认对齐数(这个我们用过)


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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Excel使用VLOOKUP查询数据

VLOOKUP函数在百度百科中的解释是&#xff1a; 解释一下&#xff0c;函数需要4个参数&#xff1a; 参数1&#xff08;lookup_value&#xff09;&#xff1a;需要匹配的值参数2&#xff08;table_array&#xff09;&#xff1a;在哪个区域里进行匹配参数3&#xff08;col_index…

复费率电表和预付费电表有哪些区别?

随着科技的发展和能源管理的日益严格&#xff0c;电表技术也在不断更新换代。复费率电表和预付费电表作为两种主流的智能电表&#xff0c;各自具有独特的优势和应用场景。接下来&#xff0c;小编来为大家详细解析这两种电表的区别及其应用场景。 一、复费率电表 1.定义及工作原…

【LeetCode刷题】--67.二进制求和

67.二进制求和 方法&#xff1a;模拟计算 class Solution {public String addBinary(String a, String b) {StringBuilder ans new StringBuilder();int carry 0;for(int ia.length()-1,jb.length()-1;i>0||j>0;i--,j--){int sum carry;sum i >0 ? a.charAt(i) …

Linux:VMWare启动虚拟机导致电脑蓝屏并重启问题解决

情况描述&#xff1a; 我这边安装Ubuntu系统后&#xff0c;对Ubuntu进行了汉化操作&#xff0c;重启Ubuntu时&#xff0c;直接导致Windows10蓝屏重启。 解决办法&#xff1a; 1、打开任务管理器&#xff0c;检查Windows10系统是否开启虚拟化功能。 没有开启&#xff0c;就进入…

linux上的通用拍照程序

最近因为工作需要&#xff0c;在ubuntu上开发了一个拍照程序。 为了找到合适的功能研究了好几种实现方式&#xff0c;在这里记录一下。 目录 太长不看版 探索过程 v4l2 QT opencv4.2 打开摄像头 为什么不直接打开第一个视频节点 获取所有分辨率 切换摄像头 太长不看…

2020年06月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行以下脚本后舞台上的角色将 ? A:先克隆自身,克隆体出现后被删除。 B:先克隆自身,克隆体出现后删除本体。 C:克隆出自身后本体与克隆体同时被删除。 D:克隆出自身后本体与克…

Rola详解国外住宅IP代理选择的8个方法,稳定的海外IP哪个靠谱?

一、国外住宅IP代理是什么&#xff1f; 代理服务器充当您和互联网之间的网关。它是一个中间服务器&#xff0c;将最终用户与他们浏览的网站分开。如果您使用国外代理IP&#xff0c;互联网流量将通过国外代理服务器流向您请求的地址。然后&#xff0c;请求通过同一个代理服务器…

python opencv 放射变换和图像缩放-实现图像平移旋转缩放

python opencv 放射变换和图像缩放-实现图像平移旋转缩放 我们实现这次实验主要用到cv2.resize和cv2.warpAffine cv2.warpAffine主要是传入一个图像矩阵&#xff0c;一个M矩阵&#xff0c;输出一个dst结果矩阵&#xff0c;计算公式如下&#xff1a; cv2.resize则主要使用fx&…

python之TCP的网络应用程序开发

文章目录 版权声明python3编码转换socket类的使用创建Socket对象Socket对象常用方法和参数使用示例服务器端代码客户端代码 TCP客户端程序开发流程TCP服务端程序开发流程TCP网络应用程序注意点socket之send和recv原理剖析send原理剖析recv原理剖析send和recv原理剖析图 多任务版…

【管理运筹学】背诵手册(五)| 动态规划

五、动态规划 基本概念 阶段&#xff08;Stage&#xff09;&#xff1a;将所给问题的过程&#xff0c;按时间或空间特征分解成若干相互联系的阶段&#xff0c;以便按次序去求解每阶段的解&#xff0c;常用字母 k k k 表示。 状态&#xff08;State&#xff09;&#xff1a;…

iOS APP包分析工具 | 京东云技术团队

介绍 分享一款用于分析iOSipa包的脚本工具&#xff0c;使用此工具可以自动扫描发现可修复的包体积问题&#xff0c;同时可以生成包体积数据用于查看。这块工具我们团队内部已经使用很长一段时间&#xff0c;希望可以帮助到更多的开发同学更加效率的优化包体积问题。 工具下载…

HTB Codify WriteUp

Codify 2023年11月7日 20:59:48user nmap ➜ Codify nmap -A 10.10.11.239 Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-07 21:00 CST Nmap scan report for bogon (10.10.11.239) Host is up (0.14s latency). Not shown: 997 closed ports PORT STATE SERVI…

An issue was found when checking AAR metadata

一、报错信息 An issue was found when checking AAR metadata:1. Dependency androidx.activity:activity:1.8.0 requires libraries and applications that depend on it to compile against version 34 or later of the Android APIs.:app is currently compiled against …

spark的算子

spark的算子 1.spark的单Value算子 Spark中的单Value算子是指对一个RDD中的每个元素进行操作&#xff0c;并返回一个新的RDD。下面详细介绍一些常用的单Value算子及其功能&#xff1a; map&#xff1a;逐条映射&#xff0c;将RDD中的每个元素通过指定的函数转换成另一个值&am…

009 OpenCV 二值化 threshold

一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、二值化算法 2.1、概述 在机器视觉应用中&#xff0c;OpenCV的二值化函数threshold具有不可忽视的作用。主要的功能是将一幅灰度图进行二值化处理&#xff0c;以此大幅降低图像的数…

app小程序定制的重点|软件定制开发|网站搭建

app小程序定制的重点|软件定制开发|网站搭建 App小程序定制开发是近年来快速发展的一项技术服务&#xff0c;随着移动互联网的普及和用户需求的不断升级&#xff0c;越来越多的企业和个人开始关注和需求定制化的小程序开发。那么&#xff0c;对于app小程序定制开发来说&#xf…

第十五届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组(详细分析解答)

目录 1.动态的Tab栏 1.1 题目要求 1.2 题目分析 1.3 源代码 2.地球环游 2.1 题目要求 2.2 题目分析 2.3 源代码 3.迷惑的this 3.1 题目要求 3.2 题目分析 3.3 源代码 4.魔法失灵了 4.1 题目要求 4.2 题目分析 4.3 源代码 5.燃烧你的卡路里 5.1 题目要求 5.2…

U-boot(四):start_armboot

本文主要探讨210的uboot启动的第二阶段&#xff0c;主要函数为start_armboot。 uboot 一阶段初始化SoC内部部件(看门狗、时钟等),初始化DDR,重定位 二阶段初始化其余硬件(iNand、网卡芯片)以及命令、环境变量等 启动打印硬件信息,进入bootdelay,读秒完后执行bootc…

(2023码蹄杯)省赛(初赛)第二场真题(原题)(题解+AC代码)

题目1&#xff1a;MC0214捡麦子 码题集OJ-捡麦子 (matiji.net) 思路: 1.第n米在前n-1米的基础上多加一个n个麦子&#xff0c;那么直接从1开始枚举&#xff0c;累加答案即可 AC_Code:C #include<bits/stdc.h> using namespace std;int main( ) {int n; cin>>n;…

ES6 — ES14 新特性

一、ES6 新特性&#xff08;2015&#xff09; 1. let和const 在ES6中&#xff0c;新增了let和const关键字&#xff0c;其中 let 主要用来声明变量&#xff0c;而 const 通常用来声明常量。let、const相对于var关键字有以下特点&#xff1a; 特性varletconst变量提升✔️全局…