C语言程序环境和预处理Pt.1 - 预处理指令|预处理操作符

电脑所能识别的语言为二进制指令,而我们所写的C语言代码是文本信息。为了能使计算机识别并执行C语言代码,就需要翻译环境,使C语言代码翻译为二进制的指令。

1.按下编译按钮的幕后 - 程序的翻译环境

从C语言源代码到计算机可识别的二进制文件,每当我们按下编译按钮时,在编译器背后究竟悄悄地发生了什么?

下图是这背后的大概流程:

从图中的各个流程具体来说,翻译环境包含以下两大各阶段,共四个步骤:

1.编译:


  1. 预处理(Preprocessing):在编译之前,源代码会经过预处理器处理。预处理器指令如#include和#define会在此阶段展开,及将包含头文件到目标文件将define的符号进行替换,并且会删除注释等不必要的内容。

  2. 编译(Compilation):在编译阶段,预处理后的源代码会被编译器翻译成汇编语言。汇编语言是与特定硬件架构相关的低级代码。
    这个阶段包括语法分析,词法分析,语义分析,符号汇总。它们确保语法的正确性并对代码做出优化,提高程序运行效率。

  3. 汇编(Assembly):汇编器将汇编语言转换成机器语言,即由二进制指令组成的程序。此形成符号表,得到到目标文件

2.链接:


  1. 链接(Linking):在链接阶段,编译器将各个目标文件中的代码和数据段合并成一个可执行文件。这就是合并段表。段表包含了关于每个段(例如代码段、数据段)的信息,如起始地址、大小等。它们在这一阶段被整合到一个统一的表格中,以确保最终生成的可执行文件正确地加载和执行各个模块的代码和数据。

    在解析重复符号后,编译器会将这些符号按照一定的规则合并成一个符号表。这个过程涉及到符号解析、地址重定位等操作,确保最终生成的可执行文件中每个符号都有唯一的地址。最后,编译器将合并后的符号表与其他段(如代码段、数据段等)一起生成最终的可执行文件。

各阶段详解:

2.预处理详解

2.1预定义符号

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

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

 我们可以在代码中直接引用它们来获得一些相关信息,比如:

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

2.2 #define 定义标识符

#define 是C语言中的预处理指令。通过#define ,可以为一个标识符定义一个常量值、一个字符串或者一段代码片段等。

通过使#define,可以使程序更具可读性可维护性,并且可以减少代码中的重复部分,提高代码的复用性,并且,还可以用于简化代码(如下面给出的例子)。但要注意,宏定义并不会进行类型检查,而且可能会引入一些副作用,因此在使用时需要谨慎。

#include<stdio.h>
#define CASE break;case

int main()
{
	int a = 1;
	switch (a) 
	{
	case 1:
	CASE 2:
	CASE 3:
	}

	return 0;
}

还要注意,#define后面不要加分号 ';' !这种行为可能会导致if else语句被截断,else找不到对应的if语句从而报错。比如:

#define X 100;

int main()
{
    int a = 1;
    if(a > 0)
    {
        a = X;
    }
    else
    {
        a = -X;
    }

    return 0;

}

 这样的代码在经过预处理截断后,得到的文本代码是:

#define X 100;

int main()
{
    int a = 1;
    if(a > 0)
    {
        a = 100;;
    }
    else
    {
        a = -100;;
    }

    return 0;

}

可以看到,a = 100 后两个分号一个作为本行代码的结束,另一个分号就只能成为if语句的结束标志了。

2.3 #define 定义宏

宏是C语言中强大的功能之一,合理运用可以使代码简化,美观。宏的参数列表几乎可以是任何C语言支持的数据类型,比如字符串,指针甚至结构体。

#define MALLOC(num, type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p = MALLOC(126, int);

	free(p);

	return 0;
}

比如在这段代码中,我们定义了一个叫MALLOC的宏,它能接收数据类型type,这是函数调用绝对做不到的,这样的宏让我们可以简化一些繁琐的的代码,使代码简洁美观。

当然,宏配合一些操作符的使用还能使宏实现更强大的功能,下面展开来详细说说。

#define MAX(x,y)  ((x)>(y)?(x):(y))

int main()
{
	int a = -1;
	int b = 1;
	printf("max is :%d", MAX(a, b));

	return 0;
}

这里定义了一个宏MAX,用于比较两个值的大小并返回较大的值。在程序中可以像函数一样调用MAX宏。

注意: 参数列表的左括号必须与宏名紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为宏的定义的一部分;

又由于#define允许把参数替换到文本中,所以宏定义中我们都需要给参数加上括号,以处理传来的参数是一个表达式,且表达式中的运算符的优先级没有宏定义中的运算符优先级高的情况。如

#define SQUARE(x) x*x

如果我们仅传入一个整数,宏当然能正确的给出结果,可是当传入'x+2'时,运算的优先级就会发生错乱

x + 2 * x + 2

2.4 宏替换规则

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

PS:宏不能实现递归,字符串中的定义标识符不会被替换。

2.5 #和##  - 宏的两个专属操作符

# 用于将参数转换为字符串。当它在宏定义中使用时,它可以将宏参数转换为字符串。可以简单的理解为给参数加上一个""。

#define PRINT(n) printf("the value of "#n" is %d\n", n)
int main()
{
	int a = 10;

	PRINT(a);

	return 0;
}

在这段代码中,#n被替换为字符串n,即"n",由于字符串是有自动连接的特点,宏替换后将"n"放入printf函数中就可以正常实现打印功能了。

为了处理各种格式的正常打印,我们同样可以用宏替换来实现,例如:

#define PRINT(n, format) printf("the value of "#n" is "format"\n", n)
int main()
{
	float a = 10.3;

	PRINT(a, "%f");

	return 0;
}

 ## 用于连接两个参数。当它在宏定义中使用时,它可以将两个参数连接在一起。

#define symbol_cat(x,y) x##y
int main()
{
	int ab = 1;
	printf("%d", symbol_cat(a, b));

	return 0;
}

2.5 几个带副作用的宏参数

先理解什么使副作用

就和生活中一样,比如感冒了喝了感冒灵,虽然能缓解感冒症状,但会使人产生睡意,对白天的工作学习造成影响。

C语言中的副作用也类似,就是指在实现了代码目的的同时造成了一些想要的结果,这就是副作用,下面通过一段代码来理解:

void print(int* p)
{
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(p++));
	}
}

int main()
{
	int num[5] = { 1,2,3,4,5 };

	print(num);

	return 0;
}

在这段代码中,在实现打印功能时,指针p发生了移动,在打印完成后,它不再指向数组的开头而是指向了数组的末尾,这不是我们想要的,我们就称这是一个副作用。 


当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。 比如:

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 1;
	int b = 2;
	int c = MAX(a++, b++);
	//int c = ((a++) > (b++) ? (a++) : (b++));

	printf("a:%d b:%d c:%d", a, b, c);

	return 0;
}

在这段代码中,b自增了两次, 产生了副作用。

所以,在传宏参数时,要避免传入可能产生副作用的参数,以免引发问题。

2.6 宏与函数的对比

(1)宏通常比函数调用更快。因为宏展开是简单的文本替换,在编译时就已经完成了。而函数用需要在运行时进行调用,包括函数入栈、返回地址保存等操作,相对而言会稍微慢一些。所以宏比函数在程序的规模和速度方面更胜一筹。

举一个例子:

#define MAX(x,y) ((x)>(y)?(x):(y))

int max(int x, int y)
{
	if (x > y)
	{
		return x;
	}
	return y;
}
int main()
{
	int a = 1;
	int b = 2;
	int c = MAX(a, b);
	int d = max(a, b);
	

	printf("a:%d b:%d c:%d d:%d", a, b, c, d);

	return 0;
}

 这个例子中分别通过宏和函数来实现了求两个数的最大值。我们通过反汇编代码来观察两种方式的执行复杂度:

​​

(宏MAX的反汇编代码) 


 

 (函数max的反汇编代码) 


由此可以看出,函数相较于宏在执行时有更多的准备工作,包括函数入栈、返回地址保存等操作(详见函数栈帧) 

(2)宏没有类型检查,函数有类型检查: 宏在展开时只是简单的文本替换,不进行类型检查,因此可能导致一些潜在的错误。而函数在编译时会进行类型检查,可以帮助捕获一些类型错误。 

 

(3)宏展开可能会导致代码膨胀,而函数调用则不会: 使用宏可能会导致代码的体积增大,因为宏展开是文本替换,会将宏的内容完全复制到每个调用点。而函数调用只需要在代码中保存一份函数的定义,多次调用时只需要保存函数地址,不会增加代码的体积。

(4) 宏不能调试,而函数可以。宏名称在调试器中不会被识别,因此在调试时无法像函数调用那样单步跟踪或设置断点来查看宏的执行过程。

 3. 黑框框后面发生的事 - 程序的运行环境

  1. 加载(Lading):程序必须先载入内存中,并未程序分配内存空间。这个过程由操作系统来完成,只有在独立的环境中,程序的载入必须由手动安排。
     
  2. 执行(Execution):程序开始执行,接着便调用main函数,执行相应指令。
     
  3. 运行时(Runtime):程序将使用一个运行时栈(stack),储存函数的局部变量和返回值。程序同时也可以使用静态(stack)内存,储存于静态内存中的变量在程序的整个执行过程中都会被保留。
     
  4. 终止(Termination):终止main函数(可能是正常结束也有可能是异常退出),系统收回分配给程序的资源。

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

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

相关文章

MTK安卓开发板_联发科开发板评估套件_安卓主板硬件开发

在介绍开发板之前&#xff0c;让我们先来区分一下核心板和开发板的区别。核心板是一种集成度高、功能完整的计算模块&#xff0c;搭载系统&#xff0c;简化了外围接口&#xff0c;体积尺寸相对较小&#xff0c;主要适用于嵌入式系统。而开发板由核心板底板组成&#xff0c;提供…

spring中事务失效的场景有哪些?

异常捕获处理 在方法中已经将异常捕获处理掉并没有抛出。 事务只有捕捉到了抛出的异常才可以进行处理&#xff0c;如果有异常业务中直接捕获处理掉没有抛出&#xff0c;事务是无法感知到的。 解决&#xff1a;在catch块throw抛出异常。 抛出检查异常 spring默认只会回滚非检…

EI级 | Matlab实现PCA-GCN主成分降维结合图卷积神经网络的数据多特征分类预测

EI级 | Matlab实现PCA-GCN主成分降维结合图卷积神经网络的数据多特征分类预测 目录 EI级 | Matlab实现PCA-GCN主成分降维结合图卷积神经网络的数据多特征分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现PCA-GCN主成分降维结合图卷积神经网络的数据多…

c/c++ | 求叶子结点个数 |构建B树 | 动态规划--找叶子结点个数

是这样的&#xff0c;一道代码题&#xff0c;根据输入数据&#xff0c;计算运行结果 #include<bits/stdc.h> using namespace std; vector<int>g[10]; int ans 0; void dfs(int x){if(g[x].size() 0){ans;return;}for(int i 0; i < g[x].size(); i){dfs(g[x]…

windows 安装 gitlab-runner CICD

点击搜索图标 手动输入PowerShell, 右键点击管理员权限打开&#xff0c; 一、安装 安装 gitlab runner 文档参考地址 1、下载exe执行文件 我这里是 win64 https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe 2、创建 gitla…

基于java+springboot+vue实现的电影订票系统(文末源码+Lw+ppt)23-41

摘 要 随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;计算机如今已成为人们生活中不可缺少的一部分&#xff0c;为电影订票方便管理&#xff0c;基于java技术设计与实现了一款简洁、轻便的管理系统。本系统解决了电影订票事务中的主要问题&#xff0c;包括个…

08.JavaScript中的编程思想,构造函数和原型对象

一、编程思想 学习 JavaScript 中基于原型的面向对象编程序的语法实现&#xff0c;理解面向对象编程的特征。 1.面向过程 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次 调用就可以了。 举个…

JAVA初阶数据结构栈(工程文件后续会上传)(+专栏数据结构练习是完整版)

1.栈的概念讲解(Stack)&#xff09; 定义&#xff1a;栈是一种先进后出的数据结构 要想拿到12就要把它头上的所有东西给移出去 2.栈的实现&#xff08;代码&#xff09; 2.1栈的方法逻辑的讲解 &#xff08;1&#xff09;新建一个测试类Frank &#xff08;2&#xff09;进…

基于STM32的智慧农业管理系统设计与实现

文章目录 一、前言1.1 项目介绍【1】项目功能【2】设计实现的功能【3】项目硬件模块组成 1.2 设计思路1.3 传感器功能介绍1.4 开发工具的选择 二、EMQX开源MQTT服务器框架三、购买ECS云服务器3.1 登录官网3.2 购买ECS服务器3.3 配置安全组3.4 安装FinalShell3.5 远程登录到云服…

后端给前端导出 数据excal表

pom <!-- 读取文档 --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.15</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><…

最新CLion + STM32 + CubeMX 开发环境搭建

网上有不少相关教程&#xff0c;但都是基于老版本Clion&#xff0c;新版有一些改变&#xff0c;但整体是简单了。 PS&#xff1a;本教程基于CLion 2023.3.4 安装所需工具参考&#xff1a;Clion搭建stm32开发环境&#xff08;STM32F103C8T6&#xff09;&#xff0c;有这一篇就够…

BFS(宽度优先搜索)C++(Acwing)

代码&#xff1a; #include <cstring> #include <iostream> #include <algorithm>using namespace std;typedef pair<int, int> PII;const int N 110;int n, m; int g[N][N]; int d[N][N]; PII q[N * N];int bfs() {int hh 0, tt 0;q[0] {0, 0};m…

C#,图论与图算法,输出无向图(Un-directed Graph)全部环(cycle)的算法与源代码

1 无向图(Un-directed Graph)全部环 图算法中需要求解全部的环。 2 方法 使用图着色方法,用唯一的数字标记不同循环的所有顶点。图形遍历完成后,将所有类似的标记数字推送到邻接列表,并相应地打印邻接列表。 3 算法 将边插入到邻接列表中。调用DFS函数,该函数使用着色方…

完整的通过git命令框和windows窗口将本地文件上传到gitee远程仓库流程步骤

1.下载git 这个网站搜索git官方&#xff0c;去下载就行了 2.打开git安装后的Git Bash命令框 3.在Git Bash命令框设置一下要远程链接的gitee账号 git config --global user.name “名字”Git config --global user.email “邮箱” 4.查看一下账号设置 git config --global -…

解释multi_update_all函数

import dgl import dgl.function as fn import torch# 实例化一个异构图 g dgl.heterograph({(user, follows, user): ([0, 1], [1, 1]),(game, attracts, user): ([0], [1]) }) g.nodes[user].data[h] torch.tensor([[1.], [2.]]) g.nodes[game].data[h] torch.tensor([[1.…

springboot整合最新版minio和minio的安装(完整教程,新人必看)

概述&#xff1a;这种东西&#xff0c;多写点&#xff0c;方便以后自己使用 目录 第一步&#xff1a;docker安装配置minio 第一步&#xff1a;拉取镜像 第二步&#xff1a;创建用于存储MinIO数据的卷 如果是最新版minio直接就使用最后的那个命令创建容器 第三步&#xff…

coreldraw是什么软件 矢量图设计软件推荐 CDR学习入门 CDR2024发布 cdr2024免费激活下载

CDR是一款平面设计软件&#xff0c;全称为CorelDRAW&#xff0c;是由Corel公司开发的矢量图形编辑工具套件。这款软件结合了矢量图形设计和页面布局的全功能&#xff0c;被广泛应用于商标设计、标志制作、模型绘制、插图描画、排版及分色输出等诸多领域。 2024年3月&#xff0…

力扣思路题:重复的子字符串

注意比较j与j-i是否相同 bool repeatedSubstringPattern(char* s) {int i;int nstrlen(s);bool flag;for(int i1;i<n/2;i){if(n%i0){flagtrue;}for(int ji;j<n;j){if(s[j]!s[j-i]){flagfalse;break;}}if(flagtrue){return true;}}return false; }

力扣106 从中序与后续遍历序列构造二叉树

文章目录 题目描述解题思路代码 题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], …

有来团队后台项目-解析8

UnoCss 介绍 UnoCss 官网UnoCss 官网 安装 pnpm add -D unocss引入 vite.config.ts import UnoCSS from unocss/vite // plugins 中引入 UnoCSS({/* options */ }),创建uno.config.ts // uno.config.ts import {defineConfig,presetAttributify,presetIcons,presetTyp…