C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)

news2024/9/30 1:41:10

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
​🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


程序环境和预处理

  • **作者前言**
  • 环境
  • 源码![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d80a5dc086fe48459f6a12aeb961ae24.png)
  • 编译环境
    • 编译
      • 预处理
      • 编译
      • 汇编
    • 链接
    • 可执行程序
  • 运行环境
  • 预处理详解
    • #define
    • #和##
      • \#
    • \##
    • 带有副作用的宏参数
    • 宏和函数的比较
  • 函数和宏的命名规则
    • undef
  • 命令行定义
  • 条件编译
    • #if .... #endif
    • 多分支语句
    • 判断是否被定义
  • 文件包含
  • 嵌套文件包含

环境

一个程序的运行要经历两个环境分别是
(1)翻译环境:在这个环境中的源码被转变成可执行的机器指令
(2)执行环境,用于实际执行代码

源码在这里插入图片描述

图中的就是源码,源码存放在.c后缀的文件里
在这里插入图片描述
我们在运行这个文件的时候就会生成一个exe可执行文件,这个可执行文件是经过一系列的翻译得来的(翻译环境),exe可执行文件里面的内容是二进制指令
当我们运行这个exe可执行文件,就会显示打印的内容(执行环境)
下面我将围绕这个进行
所以我理解成一个图例

编译环境

编译环境分为编译和链接
在这里插入图片描述
源文件通过编译(经过编译器cl.exe) 编译成目标文件obj ,然后目标文件通过链接器和链接库进行链接,成就了一个可执行程序

编译

当我们查看对应的文件
在这里插入图片描述
会生成一个obj文件(二进制文件),这个就是day27_1.c生成的,经过cl.exe编译器处理
在这里插入图片描述
需要注意的是,在windos环境下生成的是obj文件,如果是在Linux里面生成 的就是.o文件
在这个编译的主要还要分成三部分
预处理(预编译)、编译和汇编

预处理

在这里插入图片描述
在vs2019里面找到这个界面,我们把这个预处理到文件更改为是,然后运行出来,过程会报错,这个正常,我们查看文件情况
在这里插入图片描述
后缀.i的文件就是预处理完的文件,
,当我们查看一下里面的内容,到文件末尾找到
在这里插入图片描述
那如果我们在Linux环境下运行gcc,看看

gcc -E test1.c -o test1.i #-E代表是生成预处理文件,-o是指定到哪个文件

在这里插入图片描述
我们查看一下
在这里插入图片描述
发现里面的情况大致和在windows环境下的一样,
所以我们知道
预处理:
(1)把注释替换成立空格
(2)头文件的包含处理
(3)#define 的符号替换
我们知道 有#的符号的代码 可以认为是预处理指令,如 #include #define 这些都是在预处理的阶段进行的

编译

我们在Linux系统下操作

gcc -S test1.i -o test1.s

然后查看里面的内容
在这里插入图片描述
就会发现这里是汇编代码
总结:
把C语言代码翻译成汇编代码,过程很复杂,要进行词法分析, 语法分析 和语句分析 和符号汇总(汇总的都是全局的

汇编

把汇编代码翻译成二进制指令生成了.o文件(目标文件),也生成了一个符号表(一个.c文件产生一个符号表)

gcc -c test1.s -o test1.o
或者
gcc -c test1.s 

当我们查看这个文件的时候
在这里插入图片描述

链接

在这里插入图片描述
这个就是我们的链接器了
我们需要链接的就是.obj文件和链接库

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

在Linux系统中gcc编译器生成的目标文件和二进制的可执行文件都是按照elf这种文件的形式组织的
链接过程是把所有的目标文件.o进行合并(合并段表),也会进行符号表的合并
在这里插入图片描述
符号表的合并和重定向
一个文件写了add函数,另外一个文件引入这个函数,
在这里插入图片描述
总结:
(1)合并段表
(2) 符号表的合并和重定向

可执行程序

gcc test1.o -o test

运行这个代码就会生成一个可执行程序
在这里插入图片描述
或者我们可以

gcc test1.c -o test1.out

可以直接生成可执行程序

运行环境

程序执行的过程:

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

预处理详解

__FILE__    //进行编译的源文件
__LINE__   //文件当前的行号
__DATE__   //文件被编译的日期
__TIME__   //文件被编译的时间
__STDC__   //如果编译器遵循ANSI C,其值为1,否则未定义

在vs2019中__STDC__ 不支持

#include<stdio.h>
#define M 100
int main()
{
	printf("%s\n", __FILE__);//查看当前的文件路径
	printf("%d\n", __LINE__);//查看这一行是第几行
	printf("%s\n", __DATE__);//查看当前日期
	printf("%s\n", __TIME__);//查看当前时间


	return 0;
}

在linux的gcc可以
在这里插入图片描述

#define

(1)定义常量

#define M 100

(2)定义宏

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

#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

#define M(a,b) (a+b)

我们尽量在写宏的时候给替代的数值加个()
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

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

#和##

我们知道在字符串中有#define定义的符号是不会替换的

#

使用 # ,把一个宏参数变成对应的字符串

#define print(a, format) printf("the value of " #a " is " format "\n", a)
#include<stdio.h>
#define PRINT(a, format) printf("the value of " #a " is " format "\n", a)
int main()
{
	int  b= 10;
	PRINT(b, "%d");
	return 0;
}

在这里插入图片描述
#会把宏参数变成一个字符串,不会进行转换

##

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。

#include<stdio.h>
//#define A 100
//#define M(a,b) (a+b)

#define ADD(num, value) int sum##num = value
#define print(num)  printf("%d ", sum##num)
int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		ADD(i, i);
		print(i);
	}
	return 0;
}

我们可以使用于创建一些文件名的地方,或者创建一些变量
在这里插入图片描述

带有副作用的宏参数

#include<stdio.h>
//#define a 100
//#define M(a,b) (a+b)
#define ADD(a,b) (a >= b? a + b : 1)
int main()
{
	int a = 10;
	int b = 10;
	printf("%d\n", ADD(a++, b));
	printf("a = %d", a);

	return 0;
}

可以看到我们写了一段代码里面有宏,传入的值是a++,下面预处理的代码如下
在这里插入图片描述
可以简单明了的看到a的值变化了两次
注意:我们传入参数要思考好,不然就会引起一些不必要的后果

宏和函数的比较

宏:
1.通常被应用于执行简单的运算
在这里插入图片描述
宏仅仅只有运算,
如果使用函数,不仅要为函数创建栈帧,参数的传递,还有运算, 最后函数返回,这个就会很费时间
2.宏比函数在程序运算的规模和速度更胜一筹
3.函数的参数要声明类型,而宏不是

函数:
1.宏每调用一次就会替换一次,如果宏很长,即不好写,也不好直观
2.宏不能调试,
3.宏的类型无关,也会不严谨
4.宏可能会造成一些运算顺序问题,戴上()频繁

建议:逻辑简单,使用宏,逻辑复杂使用函数

函数和宏的命名规则

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

undef

移除一个宏定义

#include<stdio.h>
#define M 100
int main()
{
#undef M
	printf("%d", M);
	return 0;
}

M不存在了,这里就会报错.

命令行定义

在linux系统中

#include<stdio.h>
int main()
{
	int arr[sz];
	int i = 0;
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d", arr[i]);
	}

	return 0;
}

在这里插入图片描述

条件编译

我们在写了一段代码发现,某些代码是不需要的,但是删除了可惜,因此我们在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

#if … #endif

#if 常量表达式
//...
#endif

相当于我们的if判断 ,没有else

#include<stdio.h>
#define M 3
int main()
{
	int a = 1;
	scanf("%d", &a);
#if M==3
	printf("%d", a);
#endif
	printf("跳过");


	return 0;
}

如果M==3就执行printf(“%d”, a);,不是就不执行,

多分支语句

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
#include<stdio.h>
//#define a 100
//#define M(a,b) (a+b)
#define M  10
int main()
{
#if M == 1
	printf("1 ");
#elif M == 2
	printf("2 ");
#else
	printf("3 ");
#endif
	printf("运行结束");

	return 0;
}

这个语句相当于我们的if的多分支语句

判断是否被定义

#include<stdio.h>
//#define a 100
#define M 0
int main()
{
#if defined(M)
	printf("定义了");
#endif
	printf("哈哈哈");

	return 0;
}
#include<stdio.h>
//#define a 100
#define M 0
int main()
{
#ifdef M
	printf("定义了");
#endif
	printf("哈哈哈");

	return 0;
}

#if defined(M) 等同于 #ifdef M

#include<stdio.h>
//#define a 100
//#define M 0
int main()
{
#ifdef M
	printf("定义了");
#elif !defined(M)
	printf("没有定义");
#endif
	printf("哈哈哈");

	return 0;
}

#if !defined(M) 等同于 #ifndef M
defined() 函数用于检查某个标识符是否已经被 #define 定义

文件包含

我们知道#include可以引入头文件
有两种表示形式
(1)包含本地文件(自己写的文件,或者别人写的)
#include"xxx.h"
会先源文件的目录下查找头文件,如果找不到就会到标准位置找(库函数的目录)(标准库位置)
在这里插入图片描述

(2)包含标准库的方式
#include<xxx.h>
直接到标准库里面找,找不到就报错

Linux环境的标准头文件的路径:

/usr/include

在这里插入图片描述

嵌套文件包含

我们在引入头文件可能会引入多次相同的文件
为了防止重复引用

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif  //__TEST_H__

这段代码是经典的头文件保护机制,用于防止同一头文件被多次包含。当 TEST_H 没有被定义时,会定义它并包含头文件内容,否则直接跳过。
或者

#pragma once

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

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

相关文章

一招教你如何绕过OpenAI API key创建时需要手机号验证

一招教你如何绕过OpenAI API key创建时需要手机号验证 虽然现在 ChatGPT 注册门槛极大地降低。但是&#xff0c;如果你是开发者或者需要第三方应用接入ChatGPT&#xff0c;此时就需要获取一个 API key&#xff0c;然而你可能会发现在你在创建 key 的过程中需要进行手机号验证。…

可控硅(晶闸管)原理图及可控硅工作原理分析

可控硅(晶闸管)原理图 可控硅T在工作过程中&#xff0c;它的阳极A和阴极K与电源和负载连接&#xff0c;组成可控硅的主电路&#xff0c;可控硅的门极G和阴极K与控制可控硅的装置连接&#xff0c;组成可控硅的控制电路。 从可控硅的内部分析工作过程&#xff1a; 可控硅是四层…

校园转转二手市场源码+Java二手交易市场整站源码

源码介绍 校园转转二手市场源码分享&#xff0c;Java写的应用&#xff0c;mybatis-plus 和 Hibernate随心用 后台地址&#xff1a;/home/index/index 账号密码&#xff1a;admin/123456 前台地址&#xff1a;/system/login

企业微信自动登录自定义系统

方法一&#xff1a;企业微信构造OAuth2链接跳转登录到自定义系统 企业微信自定义应用配置 构造网页授权链接 如果企业需要在打开的网页里面携带用户的身份信息&#xff0c;第一步需要构造如下的链接来获取code参数&#xff1a; https://open.weixin.qq.com/connect/oauth2/…

重新配置torch1.8 cuda11.1 torchtext0.9.0虚拟Pytorch开发环境

这里写目录标题 起因发现选择安装cuda 11.1核对下自己的显卡是否支持下载该版本的CUDACUDA下载地址CUDA安装过程在anaconda中创建一个虚拟环境1.以下是环境的配置过程2.查看虚拟环境列表3.激活虚拟环境4.输入这句代码&#xff0c;没想到就可以直接安装torch和torchtext了[网站在…

计算机基础,以及实施运维工程师介绍

目录 一.实施&#xff0c;运维工程师介绍 1.什么是实施工程师&#xff1f; 实施工程师职责 2.什么是运维工程师&#xff1f; 运维工程师职责 3.实施运维需要的技术 数据库 操作系统 网络 服务器 软件 硬件 网络 二.计算机介绍 CPU 存储器 io 总线 主板 三.操…

【lesson18】MySQL内置函数(1)日期函数和字符串函数

文章目录 日期函数函数使用具体使用案例建表插入数据建表插入数据 字符串函数函数使用具体使用案例建表插入数据测试 日期函数 函数使用 获得年月日&#xff1a; 获得时分秒&#xff1a; 获得时间戳&#xff1a; 获得现在的时间&#xff1a; 在日期的基础上加日期&#xf…

JavaWeb 学生信息管理系统

介绍 ServletMysqlJdbcjQuery 实现学生信息管理系统 学生 班级 教师 系统设置 登陆 软件架构 软件架构说明 基于ServletMysqlJdbcjQuery 实现学生信息的增删改查功能 文件目录声明 src/dao 数据库的增删改查功能src/filter 网页的过滤拦截功能src/model 登陆的实体对象信息…

深度学习 Day19——P8YOLOv5-C3模块实现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU&#xff08;如果设备上支持GPU就使用GPU,否则使用C…

[BJDCTF2020]Mark loves cat1

提示 -信息收集.git泄露php代码审计 拿到题先做信息收集 这里用dirsearch扫了下目录 ###如果没有dirsearch我在之前的这篇博客有写dirsearch的安装环境以及地址还有怎么扫buuctf里的题 [GXYCTF2019]禁止套娃1-CSDN博客 从扫描结果来看这里存在git泄露 这里使用githack拉下来git…

Java并发(二十)----synchronized原理进阶

1、小故事 故事角色 老王 - JVM 小南 - 线程 小女 - 线程 房间 - 对象 房间门上 - 防盗锁 - Monitor-重量级锁 房间门上 - 小南书包 - 轻量级锁 房间门上 - 刻上小南大名 - 偏向锁 -对象专属于某个线程使用 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值 -批量重偏向 …

从企业的角度看待WMS仓储管理系统的集成

随着全球化和数字化的发展&#xff0c;企业面临着越来越复杂的商业环境。为了满足高效运营的需求&#xff0c;许多企业开始寻求更先进、更集成的解决方案来优化他们的仓储流程。WMS仓储管理系统作为一种重要的解决方案&#xff0c;在企业中发挥着关键的作用。本文将从企业的角度…

Android studio Android SDK下载安装

我们访问地址 https://developer.android.google.cn/studio?hlzh-cn 拉下来直接点击下载 然后来下来 勾选 然后点击下载 下载好之后 我们双击打开 点击下一步 确认上面的勾选 然后下一步 这里 我们选择一下安装目录 然后点击下一步 安装 安装完之后点击进行下一步 Fin…

echarts 饼图3样式

父组件&#xff1a; <pieChartNormal :opt"contractStatics" style"width: 100%;height: 100%;" />import pieChartNormal from "./components/pieChartNormal";data() {return {contractStatics: {seriesData: [{name: 技术服务类,value:…

Gemini Pro API 详细申请步骤

Gemini Pro API 详细申请步骤 什么是 Gemini ? 上周&#xff0c;谷歌发布了 Gemini&#xff08;双子座&#xff09;&#xff0c;它是谷歌最新、最强大的人工智能模型&#xff0c;旨在迎头痛击 OpenAI 的 GPT。Gemini 在构建时考虑到了多模态性&#xff0c;这意味着它能够理解…

SG3524控制的恒流源电路图

SG3524简介 SG3524是开关电源脉宽调制型控制器。应用于开关稳压器&#xff0c;变压器耦合的直流变换器&#xff0c;电压倍增器&#xff0c;极性转换器等。采用固定频率&#xff0c;脉冲宽度调制&#xff08;脉宽调制&#xff09;技术。输出允许单端或推挽输出。芯片电路包括电…

卸载MySQL——Windows

1. 停止MySQL服务 winR 打开运行&#xff0c;输入 services.msc 点击 “确定” 调出系统服务。 我这里只有一个&#xff0c;只要是以MySQL开头的全部停止 2. 卸载MySQL相关组件 打开控制面板 —> 卸载程序 —> 卸载MySQL相关所有组件 3. 删除MySQL安装目录 一般是C:\P…

Oracle的学习心得和知识总结(三十)| OLTP 应用程序的合成工作负载生成器Lauca论文翻译及学习

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

Tomcat-指定启动jdk、修改使用的jdk版本

修改tomcat配置文件setclasspath.sh 配置文件首行增加以下代码&#xff0c;指定启动的jdk&#xff1a; export JAVA_HOME/opt/softwares/jdk1.8.0_211/ export JRE_HOME/opt/softwares/jdk1.8.0_211/jre

详解—C++ [异常]

目录 一、C语言传统的处理错误的方式 二、C异常概念 三、异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3异常安全 3.4 异常规范 四、自定义异常体系 五、C标准库的异常体系 六、异常的优缺点 6.1、C异常的优点&#xff1a; 6.2、C异常的缺点&#xff1a;…