C语言---程序环境和预处理(底层原理万字详解)

news2024/11/16 7:56:30

文章目录

  • 前言
  • 🌟一、程序的翻译环境和执行环境
  • 🌟二、详解编译+链接
    • 🌏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语言学习
🔑本章内容:C语言 — 程序环境和预处理的部分内容
送给各位💌:无论风暴把我带向何方 我都将以主人的身份上岸 你现在的迷茫和焦虑 就是因为想的太多 而做的太少
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


提示:以下是本篇文章正文内容,下面案例可供参考

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

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

在这里插入图片描述

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code).
  • 每个目标文件由链接器(linker)捆绑在一起,形或一个单一而完整的可执行程序。
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的凶数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

举例:

当我们运行一个代码时,运行成功后我们的.c文件就会形成一个.obj文件
在这里插入图片描述

🌟二、详解编译+链接

🌏2.1命令

1.预处理选项gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
⒉.编译选项gcc -s test.
编译完成之后就停下来,结果保存在test.s中。
3.汇编gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中。

🌏2.2 板书详解

下面是对编译+链接的一个简单的板书了解
在这里插入图片描述

🌏2.3运行环境

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

🌟三、预处理详解

🌏3.1预定义符号

这些预定义符号都是语言内置的。
在这里插入图片描述

#include<stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i < 10; i++)
	{
		printf("%d-----%s, %s, %s, line=%d\n", arr[i], __FILE__, __DATE__, __TIME__, __LINE__);
	}
	return 0;
}

在这里插入图片描述
VS并不是严格遵循C语言标准的但是我们在VSCode中是可以的
在这里插入图片描述
补充:
其实编译器在代码编译的时候,会对函数和变量名重命名的
在C语言中重命名的规则基本就是:加_
C++中会更加复杂
在这里插入图片描述

🌏3.2 #define

💫3.2.1 #define 定义标识符

在这里插入图片描述
注意:
#define 定义建议不要加上;,这样容易导致问题。
在这里插入图片描述

💫3.2.2 #define 定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro) 。

下面是宏的申明方式:
#define name( parament-list ) stuff
其中的parament-1ist是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
在这里插入图片描述

💫3.2.3 #define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

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

💫3.2.4 #和##

如何把参数插入到字符串中?
(1).#的作用:

把一个宏参数变成对应的字符串。
在这里插入图片描述
(2).##的作用:

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的

在这里插入图片描述

💫3.2.5 带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
在这里插入图片描述

💫3.2.6 宏和函数对比

函数调用的时间花费:
1.函数调用前准备(传参、函数栈帧空间的维护)
2.主要运算
3.函数返回,返回值的处理,函数栈帧的销毁

宏通常被应用于执行简单的运算。==

比如在两个数中找出较大的一个。
#define MAX(a,bca)>(b)?(a):(b))

#include<stdio.h>
int Max(int x, int y)
{
	return x > y ? x : y;
}
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int m1 = Max(a, b);
	printf("%d\n", m1);
	int m2 = MAX(a, b);
	printf("%d\n", m2);
	return 0;
}

那为什么不用函数来完成这个任务?
原因有二:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  • 2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

宏的缺点:当然和函数相比宏也有劣势的地方:

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  • 宏是没法调试的。
#define MAX(x,y) ((x)>(y)?(x):(y))
	int m2 = MAX(a, b);
	printf("%d\n", m2);
//例如这个代码我们预处理完后运行的代码是int m2=((a)>(b)?(a):(b));
//实际上真正执行的是这段代码但是在调试的时候我们看到的确实int m2 = MAX(a, b);
//但是编译器底层执行的是int m2=((a)>(b)?(a):(b));
//这样就不协调了所以宏不适合调试
  • 宏由于类型无关,也就不够严谨。
  • 4.宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
#include<stdio.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (*p == NULL)
	{
		//...
	}
	int* p2 = MALLOC(10, int);
	if (*p2 == NULL)
	{
		//...
	}
	float* p3 = MALLOC(3.14, float);
	return 0;
}

总结:以后功能比较简单的时候,可以采用宏来实现如果功能比较复杂,建议使用函数来实现

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

补充:C/C++引入了一个: inline的关键字内联函数:具有了函数的优点,也具有宏的优点

💫3.2.7 命令约定

一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是:|

  • 把宏名全部大写
  • 函数名不要全部大写

🌏3.3 #undef

这条指令用于移除一个宏定义。

例如如下代码,我们定义了一个M但是当我们用#undef指令移除一个宏定义M时,M就被去掉了所以第二个打印时就会报错:未声明的标识符
在这里插入图片描述

🌏3.4 命令行定义

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

🌏3.5 条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
比如说:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
在这里插入图片描述

🌏3.6 文件包含

我们已经知道,#include指令可以使另外一个文件被编译。就像它实际出现于#inc1ude指令的地方一样。
这种替换的方式很简单:

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

💫3.6.1头文件被包含的方式:

  • 本地文件包含

#include “filename”
查找策略:
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。

  • 库文件包含

#include <filename. h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误

这样是不是可以说对于库文件也可以使用 “ ” 的形式包含?==答案是肯定的,可以
但是这样做查找的效率就低些、当然这样也不容易区分是库文件还是本地文件了

💫3.6.2 嵌套文件包含

在这里插入图片描述
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

解决方法:
在这里插入图片描述


😽总结

请添加图片描述
😽Ending,今天的程序环境和预处理的内容就到此结束啦~,如果后续想了解更多,就请关注我吧,一键三连哦 ~

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

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

相关文章

第三章 SSD存储介质:闪存

3.1 闪存物理结构 闪存芯片从小到大依此是由&#xff1a;cell&#xff08;单元&#xff09;、page&#xff08;页&#xff09;、block&#xff08;块&#xff09;、plane&#xff08;平面&#xff09;、die&#xff08;核心&#xff09;、NAND flash&#xff08;闪存芯片&#…

【动手学习深度学习--逐行代码解析合集】10Dropout暂退法

【动手学习深度学习】逐行代码解析合集 10Dropout暂退法 视频链接&#xff1a;动手学习深度学习–Dropout暂退法 课程主页&#xff1a;https://courses.d2l.ai/zh-v2/ 教材&#xff1a;https://zh-v2.d2l.ai/ 1、暂退法原理 2、从零开始实现暂退法 import torch from torch i…

微服务网关技术选型:Zuul2、Gateway、OpenResty、Kong

1、简介 当使用单体应用程序架构时&#xff0c;客户端&#xff08;Web 或移动端&#xff09;通过向后端应用程序发起一次 REST 调用来获取数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表&#xff0c;并将响应返回给客户端。微…

missing-semester————1

文章目录 shell概述echoshell如何知道去哪寻找date或echo呢&#xff1f;$PATHlsman流根用户 shell概述 root1test:~$ $表示身份不是root用户 ~表示当前所在位置是"home" root1test:~$ date Sat Jul 8 02:57:44 UTC 2023输入命令&#xff0c;会被shell解析 上述执行…

静态路由配置——Cisco Packet Tracer

这里放一个用Packet Tracer 8.0实现的配置好的静态路由文件&#xff0c;配置如下 下载链接如下&#xff1a; https://wwix.lanzoue.com/ifp5T11ksnla

内嵌tomcat报错

严重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/D:/javaTools/apache-maven-bin/apache-maven-3.6.1/maven-repo/com/fasterxml/jackson/core/jackson-databind/2.10.5/jackson-databind-2.10.5.jar!/] for annotations org.apache.tomcat.util.b…

NI采集卡USB-6361多通道模拟输入采集报错解决方案

文章目录 前言一、现有例程1、前面板2、程序框图 二、采集测试1、单通道采集2、多通道采集①、错误的做法②、正确的做法1&#xff09;前面板2&#xff09;程序框图3&#xff09;运行测试 总结 前言 折腾一块 USB-6361 采集卡很久了&#xff0c;之前都是单通道采集模拟信号&am…

云原生(第六篇)k8s-kubeadmin部署

master&#xff08;2C/4G&#xff0c;cpu核心数要求大于2&#xff09; 192.168.169.10 docker、kubeadm、kubelet、kubectl、flannel node01&#xff08;2C/2G&#xff09; 192.168.169.30 docker、kubeadm、kubelet、kubect…

汇总:FlatLaf-intellij-themes皮肤效果一览

关于主题包&#xff1a; FlatLaf 是一个跨平台的 Java Swing 外观库&#xff0c;提供现代化的平面化用户界面。 导包 <dependency><groupId>com.formdev</groupId><artifactId>flatlaf</artifactId><version>3.1.1</version><sco…

机器学习28:《推荐系统-I》概述

在互联网领域&#xff0c;推荐系统&#xff08;Recommendation Systems&#xff09;的应用非常广泛。在音视频方面&#xff0c;如抖音、快手、哔哩等&#xff1b;在电商平台方面&#xff0c;如京东、淘宝、拼多多等。推荐有助于帮助用户快速发现潜在感兴趣的内容&#xff08;音…

RS485或RS232转ETHERCAT连接安川ethercat总线伺服

最近&#xff0c;生产管理设备中经常会遇到两种协议不相同的情况&#xff0c;这严重阻碍了设备之间的通讯&#xff0c;串口设备的数据不能直接传输给ETHERCAT。这可怎么办呢&#xff1f; 别担心&#xff0c;远创智控YC-ECT-RS485/232来了&#xff01;这是一款自主研发的ETHER…

使用vue ui创建vue项目失败原因

每个人的失败原因都不相同&#xff0c;因为下载NodeJS文件时&#xff0c;默认下载到c盘中&#xff0c;我改变盘符到了D盘&#xff0c;因此要删除c盘中隐藏的文件&#xff0c;注意是c盘中的.npmrc文件。具体位置如下&#xff1a; 点击查看显示隐藏文件才能看到该文件 最后创建项…

磁性材料在使用时需要注意什么

为了不引起人身损伤及磁体性能不良&#xff0c;请遵循以下注意事项&#xff1a; 1、 磁体在使用过程中应确保工作场所干净&#xff0c;否则容易吸附铁屑等磁性小颗粒影响使用。 2、 磁体在充磁时&#xff0c;磁体必须固定&#xff0c;且充磁场必须大于磁体材料矫顽力的2.5倍&…

CEC2023动态多目标优化算法:基于自适应启动策略的混合交叉动态约束多目标优化算法(MC-DCMOEA)求解CEC2023(提供MATLAB代码)

一、动态多目标优化问题 1.1问题定义 1.2 动态支配关系定义 二、 基于自适应启动策略的混合交叉动态多目标优化算法 基于自适应启动策略的混合交叉动态多目标优化算法&#xff08;Mixture Crossover Dynamic Constrained Multi-objective Evolutionary Algorithm Based on Se…

【ElasticSearch】JavaRestClient实现文档查询、排序、分页、高亮

文章目录 1、入门案例2、全文检索3、精确查询4、复合查询-boolean query5、排序和分页6、高亮 1、入门案例 先初始化JavaRestClient对象&#xff1a; SpringBootTest public class HotelSearchTest {private RestHighLevelClient client;Testvoid testInit() {System.out.pri…

uniapp如何给空包进行签名操作

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 首先安装sdk https://www.oracle.com/java/technologies/downloads/ 正常下一步即可~安装完毕后&#xff0c;进入在sdk根目录执行cmd C:\Program Files\Java\jdk-18.0.1.1\bin生成keystore 例&#xf…

数据结构--线索二叉树找前驱后继

数据结构–线索二叉树找前驱后继 中序线索二叉树找中序后继 在中序线索二叉树中找到指定结点*p的 中序后继 \color{red}中序后继 中序后继next ①若p->rtag 1&#xff0c;则next p->rchild ②若p->rtag 0 中序遍历――左根右 左根(左根右) 左根((左根右)根右) next …

PVT、OCV、工艺偏差、CPPRCRPR、ld漏级电流计算

文章目录 PVT&OCV(local variation)Sources of variation1) Etching2) Oxide Thickness propagation delay、ld、drain currentCPPR&CRPRsetup checkHold check 芯片的delay由两部分影响因素构成 cell delay&#xff1a;library set pvt_cornernet delay: rc tech fil…

电风扇自动温控调速器电路设计

这是一个电风扇自动温控调速器&#xff0c;可根据温度变化情况自动调节电风扇的转速&#xff0c;电路加以调整&#xff0c;也可用于其它电气设备的控制。 一、电路工作原理 电路原理如图 37 所示。 图中 IC 是 555 时基电路&#xff0c;与R2、R3 和 C2 等元件构成多谐振荡器…

前端Vue自定义暂无数据组件nodata 用于页面请求无数据时展示

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。通过组件化开发&#xff0c;可以有效实现单…