C语言中程序的环境和预处理简述

news2024/11/19 11:17:53

文章目录

  • 程序是如何执行的?
  • 翻译
    • 预编译
      • 条件编译
      • 文件的包含
        • 嵌套引用
        • 不同的引用方式
      • 预定义符号
    • 编译
    • 汇编
    • 链接
  • 运行


程序是如何执行的?

有时候会问自己,程序的运行是那么简单的事情吗?
我鼠标点到 visual studio 上,程序就跑起来了,然后在界面就能看到我们想要看到的输出或者是我们不想看到的错误
实际上,程序的运行会经过一个复杂的阶段之后才会被执行

这个阶段就是翻译
在这里插入图片描述
而翻译中还有许多小步骤等着我们去实现

翻译

整个翻译的过程,可以分成两个大的步骤
分别是编译和链接
在这里插入图片描述
我们的源文件,通过编译器编译可以生成目标文件(.o为后缀名的二进制文件)
所有的源文件转换为目标文件后

随后使用链接器,将工程中的多个目标文件,以及头文件引入的标准库,链接生成一个可执行程序

编译本身可以分成多个步骤,分别是
预编译,编译和汇编
在这里插入图片描述
而每一步都有独特的作用

预编译

预编译,也叫编译预处理
预编译实现的是一种文本操作
他将 define 的宏定义替代
并且执行或者忽略条件编译
并且将头文件替换进到源文件中

举个简单的例子

#define MAX 100
int main()
{
	printf("%d",MAX);
	return 0;
}

其中 define 定义的 MAX 会在预编译的环节就被替换成100


int main()
{
	printf("%d",100);
	return 0;
}

相当于复制粘贴

条件编译

有时候,有一些代码我们不想让他们运行,但是又不想删除他们
为了不让他们占用我们的使用空间,我们可以有选择地去编译他们
C语言给了几个条件编译指令

编译指令功能
#if A若条件A成立则编译
#elif B如果if A 不成立,elif B条件成立则进行编译
#else当上述条件都不成立,就进行编译
#endif结束条件编译
#ifdef DEFINE如果定义了 DEFINE(可以是任意内容) 则进行编译
#ifndef DEFINE如果没有定义 DEFINE,则进行编译
#define MAX 1
int main()
{
	#if MAX
	printf("%d\n",MAX);
	#endif 
	printf("%d",10);
	return 0;
}

如果定义了MAX 那么程序预编译后会变成这样

#define MAX 1
int main()
{
	printf("%d\n",1);
	printf("%d",10);
	return 0;
}

若没有定义 MAX 则程序预编译后 第一个 printf 会被删除


int main()
{
	printf("%d",10);
	return 0;
}

这个就是条件编译
条件编译的使用方式和 C语言的选择语句十分相似,条件编译也是可以嵌套的。

都是选择处理,但是条件编译是在预编译过程中直接对代码进行处理,后面的编译,汇编,链接都没有那些代码,大大节省了空间
不过每次条件编译都需要在最后加 #endif 来结束编译

文件的包含

预编译还有一个功能就是把引用的头文件拷贝到程序的文本里面
举个例子
如果在源文件中你

#include<stdio.h>
int main()
{
	return 0;
}

对应的引用的位置通过预编译会被替换为整个库文件内容,实际显示效果就会是这样

 <stdio.h> 的 文件内容(一大堆)
int main()
{
	return 0;
}

嵌套引用

那就意味着,如果你这样写代码

#include<stdio.h>
#include<stdio.h>
#include<stdio.h>
int main()
{
	return 0;
}

那对应 的头文件引用的位置,就会被拷贝 三次,再通过后面的 编译汇编过程,相同的代码就要编译汇编三次,相当浪费时间

或许你会说: 我怎么可能这样写代码呢?
但是你可能无意间就写成了这样

尤其是嵌套文件包含的情况

如果你是这样文件的话,就会导致嵌套文件包含

# include"comm.h"
# include"test.h"

int main()
{
	return 0;
} 

但是,comm .h 和 test .h 中内部又引用了头文件 fin.h
那在编译中,fin.h 就会被编译两次

如何避免这个问题,之前我们就讲了条件编译

#ifndef 
#define __FIN_H__ 
#endif 

通过这个条件编译,就能够达到一个效果
如果 fin.h 没有引用,就引用这个头文件(define FIN_H)
如果 已经引用 后面就不用再进行引用(不用 define FIN_H)

条件编译 #pragma once 也能达到同样的效果
他们都为了避免头文件的重复引用

不同的引用方式

曾经我们在引用头文件时候,有两种不同的引用方式

#include <stdio.h>
#include "file.h"

一种是 <> 一种是 " "
两种的找到头文件的机制不同

利用 <> 来引用头文件,是直接在头文件的标准路径下查找(编译器自动安装放在某个文件夹的路径)

一种是利用 " " 来找,这个情况,首先编译器会在源文件所在的目录下查找,如果没找到,就去标准路径下查找,会查找两个地方

预定义符号

补充一个小知识点,C语言有一些内置的预定义符号

FILE//进行编译的源文件
LINE//文件当前的行号
DATE//文件被编译的日期
TIME//文件被编译的时间
STDC//如果编译器遵循ANSI C,其值为1,否则未定义

这些符号我们是可以直接打印的,除了 STDC 可能因为编译器不是完全按照 ANSI 导致出错
因为显示的问题 , 前后有两个 __ 博客显示不出来,具体看看下面的代码

printf("%s,%d",__FILE__, __LINE__);

编译

现在我们已经能够得到这幅图,随后就是编译的过程
在这里插入图片描述
编译过程主要进行什么操作呢?
将 .c 的文本文件 变成 .s 的汇编文件
就是 里面我们的 c 语言 会被翻译 成对应 的 汇编语言

既然是翻译的过程
就要分析里面的语法,词法,语义
就有三个步骤
词法分析,语法分析,词法分析

还有一个比较重要步骤就是符号汇总,就是汇总全局符号(如 全局变量,函数等)的符号

比如你的 源文件 A/.c 中 定义了 main 函数,引用了 sum 函数 那文件 A.s 就会生成符号 _main,_sum
源文件 B.c 中定义 sum 函数 文件 B.s 会生成 _sum 的符号

汇编

我们已经经过了这些过程
在这里插入图片描述
现在我们能够得到一个.s 的汇编文件 ,最后我们要得到一个可执行的.o目标文件
汇编的作用就是得到一个独立的二进制.o文件

汇编会大概会经过两个过程

  1. 得到符号表
  2. 将汇编语言翻译成二进制语言,得到单独二进制文件

这里主要讲一下符号表是个什么东西

之前在汇编过程中,我们得到了符号汇总
假设我们的工程有两个源文件
一个是 main .c 文件

extern int sum(int,int);
int main()
{
	sum(1,2);
	return 0;
}

一个是 sum.c 文件

int sum(int a,int b)
{
	return a+b;
}

通过编译汇编后,会得到符号表
符号表包含 符号名称 和 符号地址

在这里插入图片描述
符号表,我的理解
是能够引导执行文件到对应地址执行代码的工具

链接

到此为止,我们已经将每一个单独的文件都编译完了
在这里插入图片描述

最后一步就是链接
在这里插入图片描述
链接实现两种功能

  1. 合并段表
  2. 符号表的合并和重定位

段表可以理解为程序段的整合,但这不是重点
重要的是 符号表
回到这幅图
在这里插入图片描述
还是这个例子,链接就会把两个文件整合到一起
符号表示用来帮助找到运行程序的,main 中的 sum 程序没有内容,在运行时就要借助整合后的符号表跑到 sum.c 文件的 sum 函数的地址运行
而链接就是删除没有用的 main .c 的sum 的符号,然后给有用的 sum.c 的 sum函数一个空间,并且保存这个地址

main.c 的符号会包含 _sum 但是在main 文件中,sum 没有任何意义,如果找不到对应的运行程序,链接就会报错,导致 Link 的链接错误

最后我们就能得到这个
在这里插入图片描述

运行

一共四步

  1. 程序载入内存,有操作系统,操作系统自动载入,没有操作系统,手动载入
  2. 调用main函数
  3. 执行代码,用栈储存局部变量和函数调用,并且用静态内存储静态变量
  4. 终止程序

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

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

相关文章

项目管理:如何使用甘特图制定项目计划

甘特图能够很直观地显示项目任务、进度随着时间推进的进展情况。 横轴为时间&#xff0c;纵轴为项目事项&#xff0c;用条形图的方式直观地展现项目中所有任务的时间及进度。 它可以直观地表明项目中有哪些任务&#xff1f;任务计划在什么时候进行&#xff0c;及实际进展与计划…

基于Vue+node的图书馆座位预约选座管理系统

目 录 一、绪论 1 &#xff08;一&#xff09;选题背景简介 1 &#xff08;二&#xff09;目的和意义 2 &#xff08;三&#xff09;基本内容及目标 2 二 技术简介 4 &#xff08;一&#xff09; React 4 &#xff08;二&#xff09;Vue 4 &#xff08;三&#xff09;Egg.js 5 …

生产者消费者模型

什么是生产者消费者模型&#xff1f; 生产者和消费是操作系统中一种重要的模型&#xff0c;它描述的是一种等待和通知的机制。一、概念引入 日常生活中&#xff0c;每当我们缺少某些生活用品时&#xff0c;我们都会去超市进行购买&#xff0c;那么&#xff0c;你有没有想过&am…

Node.js之Hello World

目录 简介 1.安装 2.安装后的效果 3.准备尝试运行的代码 4.运行JS代码 5.访问正在运行的代码(Hello World) 简介 Node.js 是一个基于"Chrome V8 引擎" 的JavaScript "运行环境"&#xff1b; NodeJS不是一门编程语言, NodeJS是一个运行环境&#xff…

C语言题解 | 消失的数字轮转数组

… &#x1f333;&#x1f332;&#x1f331;本文已收录至&#xff1a;C语言题解系列 更多知识尽在此专栏中&#xff01; &#x1f389;&#x1f389;&#x1f389;欢迎点赞、收藏、关注 &#x1f389;&#x1f389;&#x1f389;文章目录&#x1f349;前言&#x1f349;正文&…

这才是使用ps命令的正确姿势

这才是使用ps命令的正确姿势 前言 在linux系统当中我们通常会使用命令去查看一些系统的进程信息&#xff0c;我们最常使用的就是 ps (process status)。ps 命令主要是用于查看当前正在运行的程序&#xff0c;以及他们相关的的信息&#xff0c;我们可以通过不同的选项进行查看…

全球名校AI课程库(12)| CMU卡内基梅隆 · 数据库系统进阶课程『Advanced Database Systems』

&#x1f3c6; 课程学习中心 | &#x1f6a7; 计算机基础课程合辑 | &#x1f30d; 课程主页 | &#x1f4fa; 中英字幕视频 | &#x1f680; 项目代码解析 课程介绍 CMU 15-721 是 CMU 卡内基梅隆大学开放的数据库方向进阶课程&#xff0c;讨论了很多数据库方向新的技术研究方…

基于Matlab使用跟踪筛选器跟踪机动目标仿真(附源码)

此示例演示如何使用各种跟踪筛选器跟踪机动目标。该示例显示了使用单个运动模型和多个运动模型的滤镜之间的差异。 一、定义方案 在此示例中&#xff0c;定义了一个目标&#xff0c;该目标最初以 200 m/s 的恒定速度行进 33 秒&#xff0c;然后输入 10 度/秒的恒定转弯。转弯…

【C++】类和对象(中)

类和对象是面向对象语言的一个重要部分&#xff0c;承接上一话&#xff0c;我们直接开始这一章的内容。 文章目录 一、类的6个默认成员函数二、构造函数三、析构函数四、拷贝构造函数五、赋值操作符重载六、 const成员函数七、取地址及const取地址操作符重载八、相关习题的练习…

Windows系统常用网络命令详解及命令示例(全)

1.最基本也是最常用的&#xff0c;PING ping 192.168.0.8 -t 测试物理网络   &#xff0c;参数-t是等待用户中断测试   2.检查DNS、IP、Mac等   A. Win98&#xff1a;winipcfg   B.Win2000及以上&#xff1a;ipconfig /all      C.NSLOOKUP&#xff1a;如果查看河北…

初阶c语言之浅识指针

学习导航:> 1.指针是什么&#xff1f; 2.指针和指针类型 2.1指针-整数 2.2指针的解引用 3.野指针 3.1野指针成因 3.2如何规避野指针 4.指针运算 4.1指针-整数 4.2指针-指针 4.3指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 1.指针是什么&#xff1f; 指…

动态路由协议解析(rip)

因为今天是1024程序猿节&#xff0c;小刘就在这里祝各位程序猿大佬们节日快乐啦 作者介绍&#xff1a; &#x1f4b0;作者&#xff1a;小刘在C站 ✨每天分享课堂笔记 &#x1f338;夕阳下&#xff0c;是最美的绽放 目录 动态路由协议和 rip 1.动态路由 2.动态路由协议工作…

非线性方程求根——牛顿迭代法

一、牛顿法 1.实质&#xff1a;牛顿法实质上是一种线性方法&#xff0c;其基本思想是将非线性方程f(x)0逐步归结为某种线性方程来解。 2.牛顿法公式&#xff1a; 已知方程f(x)0有近似解xk,假设&#xff0c;将f(x)在点xk泰勒展开&#xff0c;有则方程f(x)0可近似表示为&#…

【开关电源六】buck电路的输出纹波定量分析走一波?

在TI官网发现一篇关于降压开关电源的输出纹波定量分析&#xff0c;看完觉得挺不错的&#xff0c;于是大概整理翻译了一下分享出来。以往我们分析开关电源的一次纹波都是定性分析&#xff0c;知道输出电压纹波和电感纹波电流、输出滤波电容ESR等有关&#xff0c;今天我们用数学、…

再看 Logback 源码

三大组件 Logback 构建在三个主要的类上&#xff1a;Logger&#xff0c;Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。 Logger 类作为 logback-classic 模块的一部分。Appender 与 Layouts 接口作为 logback-core 的…

VapSR

1024刚过还得搬砖 注意机制在设计高级超分辨率&#xff08;SR&#xff09;网络中起着关键作用。在这项工作中&#xff0c;作者们通过改进注意机制设计了一个高效的SR网络。VapSR以更少的参数优于当前的轻量级网络。董超团队开源超大感受野注意力超分方案 paper&#xff1a;ht…

【数据挖掘 | 可视化】 WordCloud 词云(附详细代码案例)

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 开发环境 编辑器&#xff1a; jupyter notebook 解释器&#xff1a; python 3.7在七夕节中&#xff0c;博主写了一篇为女友收集…

自学Vue之路——Vue介绍及基本语法

今日内容概要 前端发展介绍Vue的快速使用插值语法指令系统之文本指令指令系统之事件指令指令系统之属性指令 前端发展介绍 HTML(5)、CSS(3)、JavaScript(ES5、ES6、ES13)&#xff1a;编写一个个的页面 -> 给后端(PHP、Python、Go、Java) -> 后端嵌入模板语法 -> 后端…

Arduino基础知识

目录&#xff1a; 第1章 概述 1.1 Arduino简介 1.2 Arduino内部结构 第2章 Arduino编程 2.1 Arduino开发环境 2.2 Arduino语言概述 2.3 Arduino基本函数 第3章 Arduino通信教程 3.1 SPI通信 3.2 红外通信 3.3 WiFi通信 3.4 蓝牙通信 第4章 Arduino实验 1、接收串…

【学生管理系统】权限管理之用户管理—查询所有用户并关联相关角色

目录 一、查询所有用户&#xff08;关联角色&#xff09; 1&#xff09;后端 2&#xff09;前端 &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、查询所有用户&#xff08;关联角色&#xff09; 1&#xff09;后端 修改javaB…