C语言-程序环境和预处理

1.程序的翻译环境和执行环境

1.1翻译环境

1.2执行环境

2.预处理详解

2.1预定义符号

2.2#define

2.2.1#define定义标识符

2.2.2#define是否需要加上;

2.2.3#define定义宏

2.2.4#define替换

2.2.5#以及##的使用

2.2.6 带有副作用的宏定义

2.2.7宏定义与函数的比较


1.程序的翻译环境和执行环境

在ANSI C(标准c)中存在两个不同的环境

第一种是编译环境,在这个环境中源代码被转换为可执行的机器指令,计算机执行的是二进制的指令,但是我们所写的c语言代码是文本信息,计算机无法理解,翻译环境则将c语言的代码翻译成了二进制的指令,放在可执行程序中。

第二种是执行环境,用于执行实际的代码,即使执行二进制代码。

 源文件经过编译器处理后,生成了对应的目标文件,各种目标文件与链接库一起经过链接器的处理就形成了可执行程序。

1.1翻译环境

我们在vs下分文件写了以下两段代码进行测试:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
extern Add(int, int);
int main()
{int a = 10;int b = 20;int c = Add(a, b);printf("%d", c);return 0;
}
//test.c文件
#define _CRT_SECURE_NO_WARNINGS
int Add(int a, int b)
{return a + b;
}
//add.c文件

将程序运行之后,我们找到文件所在的位置,打开debug目录,发现生成了对应的.obj文件

同时还生成了可执行程序.exe

 以上只是笼统的介绍,下面我们将在vscode gcc环境下进行观察。

在此之前我们需要了解以下内容:

1.预处理,选项gcc -E test.c -o test.i,预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。-->test.i

2.编译 选项gcc -S test.c编译完成之后就停下来,结果存在test.s中--->test.s

3.汇编gcc -c test.c汇编完成后就停下来,结果保存在test.o文件中 --->test.c

 将代码以-o的形式打出来,观察到在代码上方多出了接近800行的代码,与此同时,原本代码中的#include<stdio.h>也不见了,其实这800行代码就是头文件#include<stdio.h>的内容,不仅如此我们发现#define定义的值也全都替换了,我们在代码中所写的注释也不见了,

其实代码在此时进行了预编译


1.2执行环境

程序执行的过程:

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

2.预处理详解

2.1预定义符号

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

我们又观察到__STDC__在vs编译的环境下报错:未定义,则说明vs不支持ANSIC

2.2#define

2.2.1#define定义标识符

语法:#define name stuff

#include<stdio.h>
#define M 100
#define STR "abc"
#define FOR for(;;)int main()
{printf("%d\n", M);printf("%s\n", STR);FOR{printf("1");}return 0;
}

 这一段代码是有错误的比如#define FOR for(;;),这只是为了说明#define 的用法。

#define DEBUG_PRINT printf("file:%s\tline:%d\t date:gs\ttime:%s\n",__FILE__,__LINE__,__DATE__,__TIME__)

 另外像上方这个#define的内容比较多,我们对其可以换行,但是换行之后会报错,我们可以使用'\'这个续行符,使用方法是在每一行的末尾加上\

#define DEBUG_PRINT printf("file:%s\tline:%d\t date:gs\ttime:%s\n",\
__FILE__,__LINE__,__DATE__,__TIME__)

2.2.2#define是否需要加上;

先来一段代码,首先这一段代码是有错误的

#include<stdio.h>
#define M 100;
int main()
{int a = 0;int b = 0;if (a > 5)b = M;elseb = -1;return 0;
}

 在vs上else会标红,什么原因呢。

如果我们将上面的代码进行一下预编译,就会出现下面的结果:

#include<stdio.h>
#define M 100;
int main()
{int a = 0;int b = 0;if (a > 5)b = 100;;elseb = -1;return 0;
}

可以看到b=100后面跟了两个分号,相当于

b=100;

(空语句);

而else不知道该与谁匹配,所以报错了

2.2.3#define定义宏

语法:#define name(parament-list) stuff

需要注意的是参数列表的左括号必须与name紧邻,若两者之间有空白存在,参数列表就会成为stuff的一部分

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 2;int b = 0;int c = MAX(a,b);printf("%d", c);return 0;
}

那么宏定义如何实现的呢?我们依然是对该段代码进行预编译

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 2;int b = 0;int c = ((a)>(b)?(a):(b));printf("%d", c);return 0;
}

 需要注意的是后面的x和y都需要分别加上括号,因为其中x,y可能是表达式,例如下面代码

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{int a = 3;int b = SQUARE(a);printf("%d", b);return 0;
}

此时代码还可以正常运行

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{int a = 3;int b = SQUARE(a+2);printf("%d", b);return 0;
}

 对于这个代码我们期待的运算结果是25,但运算结果是11,问题出在哪里,老办法先对该段代码进行预编译:

#include<stdio.h>
#define SQUARE(x) x*x
int main()
{int a = 3;int b = a+2*a+2;printf("%d", b);return 0;
}

 结合的顺序出现了问题。

你不会天真的以为只给x和y加上()就没事了吧,看下面的代码

#include<stdio.h>
#define DOUBLE(x) (x)+(x)
int main()
{int a = 3;int b =10*DOUBLE(a);printf("%d", b);return 0;
}

 我猜已经不需要分析了,你已经知道了DOUBLE后面的stuff需要整体加上()!如下:

#include<stdio.h>
#define DOUBLE(x) ((x)+(x))
int main()
{int a = 3;int b =10*DOUBLE(a);printf("%d", b);return 0;
}

2.2.4#define替换

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

2.2.5#以及##的使用

#的使用

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

在这样的情景下,我们想打印出a,b的值显得非常麻烦,那有没有简便的方法来实现这个功能呢? 封装一个函数? 经过尝试后发现这样是行不通的,打印的值的类型有很多种,封装函数无法实现,但是我们刚刚学习了宏定义,那么我们来尝试一下宏定义实现PRINT()

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

 printf可以有以下的用法,一个字符串可以分为几个字符串来打印。

#include<stdio.h>
int main()
{int a = 10;printf("hello world\n");printf("hello ""world\n");return 0;
}

 format是类型的意思

这里的#n就会变成"n"

#用来将宏参数转换为字符串,也就是宏参数的开头和末尾添加引号。

##的使用

1.可以把位于它两边的符号合成一个符号。

2.它允许宏定义从分离的文本片段创建标识符。

3.##只能在宏定义中使用

#include<stdio.h>
#define CAT(x,y) x##y
int main()
{int ab = 100;printf("%d", CAT(a, b));return 0;
}

2.2.6 带有副作用的宏定义

什么是副作用?

#include<stdio.h>
int main()
{int a = 10;int b= a + 1;printf("%d\n%d", a, b);return 0;
}

 上面这段代码旨在让b比a大1,,下面这段是有副作用的:

#include<stdio.h>
int main()
{int a = 10;int b= ++a;printf("%d\n%d", a, b);return 0;
}

 这段代码想改变b的同时也改变了a,具有一定的副作用。下面是关于副作用的代码

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5;int b = 6;int c = MAX(a++, b++);printf("%d\n", c);printf("%d\n", a);printf("%d\n",b);return 0;
}

 将宏定义的内容替换进去后得到的结果是

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5;int b = 6;int c = ((a++)>(b++)?(a++):(b++))printf("%d\n", c);printf("%d\n", a);printf("%d\n",b);return 0;
}

 首先是a++与b++比较,b++较大,则表达式1不执行,表达式2执行且b在经过比较时经历了++此时b为7则c为7,a只经过一次++,最后表达式二执行b再次++,得到结果a=6,b=8,c=7;

2.2.7宏定义与函数的比较

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
2.更为重要的是函数的参数必须声明为特定的类型所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型
宏是类型无关的。
宏的缺点:当然和函数相比宏也有劣势的地方
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没法调试的。

3.宏与类型无关,当然就不够严谨

4.宏可能带来运算优先级的问题,导致程序容易出错

属性#define定义宏函数

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

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

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

相关文章

Three.js 基础属性

三维坐标系 辅助观察坐标系 THREE.AxesHelper()的参数表示坐标系坐标轴线段尺寸大小&#xff0c;你可以根据需要改变尺寸。 // AxesHelper&#xff1a;辅助观察的坐标系 const axesHelper new THREE.AxesHelper(150); scene.add(axesHelper);材质半透明设置 设置材质半透明…

【牛牛送书 | 第四期】《高效使用Redis:一书学透数据存储与高可用集群》带你快速学习使用Redis

前言&#xff1a; 当今互联网技术日新月异&#xff0c;随着数据量的爆炸式增长&#xff0c;如何高效地存储和管理数据成为了每个公司都必须面对的挑战。与此同时&#xff0c;用户对于应用程序的响应速度和稳定性要求也越来越高。在这个背景下&#xff0c;Redis 作为一个…

打包Docker镜像时候,ARG标签如何使用?

FROM registry.cn-qingdao.aliyuncs.com/dataease/fabric8-java-alpine-openjdk8-jre:edge-chromium-11这里这个标签如何使用 ARG IMAGE_TAGRUN mkdir -p /opt/apps /opt/dataease/data/feature/full /opt/dataease/drivers /opt/dataease/plugins/defaultADD core/mapFiles/fu…

Python炒股自动化(2):获取股票实时数据和历史数据

如果你是一位大佬&#xff0c;看我前面的分享即可&#xff0c;相信你有自己的思路&#xff0c;或者已经有了成熟的策略&#xff0c;你需要的只是API接口来实现你的想法&#xff0c;前面的分享是你需要的&#xff0c;这些是给刚开始接触程序交易的朋友分享的。 前面发了股票程序…

【刷题】leetcode 1544.整理字符串

刷题 1544.整理字符串思路一&#xff08;模拟栈速解版&#xff09;思路二 &#xff08;原地算法巧解版&#xff09;思路三&#xff08;C栈版&#xff09; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff…

“快递单号时效调整秘籍:轻松掌握,高效管理!“

亲爱的物流管理者们&#xff0c;您是否曾遇到过这样的问题&#xff1a;快递单号时效单位不符合您的实际需求&#xff0c;导致管理效率低下&#xff0c;无法准确追踪物流信息&#xff1f;现在&#xff0c;我们为您带来一份快递单号时效单位调整秘籍&#xff0c;让您轻松掌握&…

第三百六十七回

文章目录 1. 概念介绍2. 方法与细节2.1 获取方法2.2 使用细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取当前系统语言"相关的内容&#xff0c;本章回中将介绍如何获取时间戳.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

http协议基础与Apache的简单介绍

一、相关介绍&#xff1a; 互联网&#xff1a;是网络的网络&#xff0c;是所有类型网络的母集因特网&#xff1a;世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上&#xff0c;大家把连接在因特网上的计算机都成为主机。万维网&#xff1a;WWW&#xff08;world…

mysql order by布尔盲注

什么是order by 在MySQL支持使用ORDER BY语句对查询结果集进行排序处理&#xff0c;使用ORDER BY语句不仅支持对单列数据的排序&#xff0c;还支持对数据表中多列数据的排序。语法格式如下 select * from 表名 order by 列名(或者数字) asc&#xff1b;升序(默认升序) selec…

ONLYOFFICE 桌面应用程序 v8.0 引入令人惊叹的全新界面、本地主题和Moodle 集成等更新!

前言 官网链接&#xff1a; ONLYOFFICE 官方网址 ​ 感谢您对ONLYOFFICE桌面应用程序的关注&#xff01;ONLYOFFICE桌面应用程序是一款功能强大、易于使用的办公套件&#xff0c;它可以帮助您实现高效的文档处理、电子表格编辑和演示文稿设计。 无论您是个人用户还是企业用户…

分披萨 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 “吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。 但是粗心服务员将披萨切成了每块大小…

【c语言】字符函数和字符串函数(上)

前言 在编程的过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了⽅便操作字符和字符串&#xff0c;C语⾔标准库中提供了⼀系列库函数~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 1. 字符分…

Vue 卸载eslint

卸载依赖 npm uninstall eslint --save 然后 进入package.json中&#xff0c;删除残留信息。 否则在执行卸载后&#xff0c;运行会报错。 之后再起项目。

pthread_exit和pehread_join函数

pthread_exit&#xff1a; 在线程中禁止调用exit函数&#xff0c;否则会导致整个进程退出&#xff0c;取而代之的是调用pthread_exit函数&#xff0c;这个函数只会使一个线程退出&#xff0c;如果主线程使用pthread_exit函数也不会使整个进程退出&#xff0c;不会影响其他线程…

【JavaScript 漫游】【022】事件模型

文章简介 本篇文章为【JavaScript 漫游】专栏的第 022 篇文章&#xff0c;对 JavaScript 中事件模型相关的知识点进行了总结。 监听函数 浏览器的事件模型&#xff0c;就是通过监听函数&#xff08;listener&#xff09;对事件做出反应。事件发生后&#xff0c;浏览器监听到…

2.23日学习打卡----初学Nginx(二)

2.23日学习打卡 目录: 2.23日学习打卡一. Nginx 虚拟主机虚拟主机的分类Nginx支持三种类型的虚拟主机配置Nginx虚拟主机单网卡多IP配置Nginx虚拟主机_基于域名虚拟主机配置Nginx虚拟主机基于多端口的配置4 二. Nginx 核心指令root和alias指令的区别return指令rewrite指令rewrit…

MySQL-行转列,链接查询

1. 行转列 1.1 示例数据准备 create table test_9(id int,name varchar(22),course varchar(22),score decimal(18,2) ); insert into test_9 (id,name,course,score)values(1,小王,java,99); insert into test_9 (id,name,course,score)values(2,小张,java,89.2); inse…

【MATLAB源码-第148期】基于matlab的BP神经网络2/4ASK,2/4FSK,2/4PSK信号识别仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 调制技术基础 调制技术是通信技术中的基础&#xff0c;它允许数据通过无线电波或其他形式的信号进行传输。调制可以根据信号的振幅、频率或相位的变化来进行&#xff0c;分别对应于ASK、FSK和PSK。 1.1 2ASK与4ASK 振幅…

西宾视频下载工具(mediadown)

一个支持多网站的视频下载工具。目前已经支持的网站有知乎、哔哩哔哩、得到、猫耳、蜻蜓FM。 西宾视频下载工具能够帮助你下载知乎知学堂、哔哩哔哩、得到课程、猫耳音频、蜻蜓FM的音视频文件。如果你是这些网站的会员&#xff0c;它还能帮你下载会员节目的音视频。 工具也不是…

【大厂AI课学习笔记NO.54】2.3深度学习开发任务实例(7)数据标注和数据集拆分

数据标注 有时我们会把特征工程和数据集的标注弄混淆&#xff0c;在普通的机器学习项目中&#xff0c;我们需要进行特征工程&#xff0c;但是在深度学习项目过程中&#xff0c;我们需要进行数据标注工作。 标注工具 在本案例中&#xff0c;使用的是开源的标注工具Labelme&am…