【轻松掌握C语言】程序环境和预处理

news2024/11/20 14:40:14

目录

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

              1、翻译环境

              2、执行环境

          二、预处理详解

              1、预定义符号

              2、#define

              3、#undef

              4、命令行定义

              5、条件编译

              6、文件包含


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

1、翻译环境,在这个环境中源代码被转换为可执行的机器指令。

2、执行环境,它用于实际执行代码。

  1、翻译环境

翻译环境的主要目标是把 test.c 转化为 test.exe,其中 test.c 需要先经过预编译转为 test.i ,然后再经过编译转为 test.s ,接着经过汇编翻译为 test.o ,最后再由链接器将目标文件 test.o 与其他目标文件、库函数文件等链接后生成可执行程序 test.exe。其中前三步由编译器完成,最后一步由链接器完成(这两个工具已经集成于VS中了),每个不同的源文件都需要分开编译,最后由链接器合并。

1、组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。

2、每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。

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

  2、执行环境

 程序执行的过程:

1、程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2、程序的执行便开始。接着便调用main函数。

3、开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。

4、终止程序。正常终止main函数;也有可能是意外终止。

二、预处理详解

  1、预定义符号

1、__FILE__       //进行编译的源文件

2、__LINE__      //文件当前的行号

3、__DATE__     //文件被编译的日期

4、__TIME__      //文件被编译的时间

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

使用方法如下:

int main()
{
	printf("%s\n", __FILE__);   //进行编译的源文件
	printf("%d\n", __LINE__);   //当前的语句所在行数
	printf("%s\n", __DATE__);   //当前编译运行的日期
	printf("%s\n", __TIME__);   //当前编译运行的时间
	return 0;
}

运行如下:

  2、#define

   (1)#define定义标识符

语法:

#define name stuff

#define MAX 1000

#define reg register              //为 register这个关键字,创建一个简短的名字

#define do_forever for(;;)     //用更形象的符号来替换一种实现

#define CASE break;case        //在写case语句的时候自动把 break写上。

// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。

#define DEBUG_PRINT printf("file:%s\tline:%d\t \                          

                                                 date:%s\ttime:%s\n" ,\

                                                 __FILE__,__LINE__ ,       \

                                                  __DATE__,__TIME__ )

用法如下:

   (2)#define定义宏

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

 语法:

#define name( parament-list ) stuff

name是指定义宏的名称,后面括号内的符号表是在定义内容过程中所使用的符号。stuff是宏背后所定义的内容。

注意:

参数列表的左括号必须与name紧邻。

如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

用法如下:

注意: 我们在定义宏的过程中,对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

   (3)#define替换规则

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

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

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

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

注意: 

1、宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。

2、当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

   (4)#和##

#可以把一个宏参数变成对应的字符串。

 首先我们看看这样的代码:

char* p = "hello ""world\n";
printf("hello", " world\n");
printf("%s", p);

 这里输出的是hello world,所以我们发现字符串是有自动连接的特点。

下面看一段代码:

#define PRINT(FORMAT,VALUE) printf("the value is "#FORMAT"\n",VALUE)
int main()
{
	PRINT("%d", 10);
	return 0;
}

运行结果如下:

我们发现,将#和FORMAT连在一起,转换成了一个字符串,上面的代码就能转换成下面:

 printf("the value is ""FORMAT"" \n",VALUE)

下面举个例子能更好的理解

int i = 10;
#define PRINT(FORMAT,VALUE) printf("the value of "#VALUE" is "FORMAT"\n", VALUE)
int main()
{
	PRINT("%d", i + 3);
	return 0;
}

 运行结果如下:

首先进行宏参数替换为printf("the value of "#i+3" is ""%d""\n",10+2) 

根据#号的作用将i+3这个宏参数变成字符串"i+3"

最后输出结果就为the value of  i+3  is  13。

## 的作用是可以把位于它两边的符号合成一个符号。

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

举个例子:

#define ADD_TO_SUM(n, value) sum##n += value;
int main()
{
	int sum5 = 3;
	ADD_TO_SUM(5, 3);
	printf("%d ", sum5);//输出结果为什么
	return 0;
}

运行如下:

 首先进行宏参数替换为sum##5 += 3 就等同于sum##5 = sum##5 + 3

根据##的作用将两边的符号合成一个符号,为sum5 = sum5 + 3

所以结果为6。

   (5)带副作用的宏参数

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

例如:

x+1;          //不带副作用

x++;          //带有副作用

举个例子:

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
	return 0;
}

 运行结果:

首先进行宏参数的替换为z = ((x++) > (y++) ? (x++) : (y++));

即在使用宏的过程中,由于宏是完全替换的,所以出现了多次++的现象。所以在使用宏的时候,我们需要谨慎地考虑。

   (6)宏和函数对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))

为什么不用函数来完成这个任务?

原因有两点:

1、用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹

2、更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

但是宏与函数比也有自己的缺点: 

1、每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

2、宏是没法调试的。

3、宏由于类型无关,也就不够严谨。

4、宏可能会带来运算符优先级的问题,导致程容易出现错。

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

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int*)malloc(10 * sizeof(int));  //而函数做不到这样

 在这里我将宏和函数做了一个鲜明的对比:

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

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

把宏名全部大写

函数名不要全部大写

  3、#undef

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

#undef NAME   

//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

  4、命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)

#include <stdio.h>
int main()
{
    int array[ARRAY_SIZE];
    int i = 0;
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        array[i] = i;
    }
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
    return 0;
}

 VS2022中不好观察,大家可以在Linux平台下去观察,

编译指令:gcc  -D  ARRAY_SIZE=10  programe.c 

  5、条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。

比如说:

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

举例说明一下:

#include <stdio.h>
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

上面的意思就是如果__DEBUG__定义了,就执行printf("%d\n",arr[i])语句,如果没有定义就不执行。

常见的条件编译指令:

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

  6、文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方 一样。

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

    (1)头文件被包含的方式

本地文件包含:

#include "filename"

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

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

/usr/include

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

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include

库文件包含:

#include  <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。对于库文件也可以使用 “ ” 的形式包含, 但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。 

    (2)嵌套文件包含

如果出现一下场景:

如何解决这个问题呢?

答案是:条件编译。

每个头文件的开头写:

#ifndef __TEST_H__

#define __TEST_H__

//头文件的内容

#endif   //__TEST_H__

或者是:

#pragma once

这样就可以避免头文件的重复引入。


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

 老铁们,记着点赞加关注哦!!! 

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

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

相关文章

笔记本怎么录制屏幕?只需2分钟,快速学会

如今&#xff0c;大多数人会在笔记本电脑上使用屏幕录制功能&#xff0c;如&#xff1a;在线直播课程、在线会议、电影和电视剧等场景。笔记本怎么录制屏幕&#xff1f;事实上&#xff0c;用电脑录制视频并不像你想象的那么困难。我们每天使用的电脑都有自己的屏幕录制功能&…

高性能数据分析时代,HPDA平台需要什么样的数据存储?

在海量基因数据中进行全基因数据分析&#xff0c;了解各种疾病与DNA之间的隐秘联系&#xff1b;对海洋气候进行预测&#xff0c;利用强大的数据分析性能&#xff0c;实现分钟级的数据刷新、精准预测海洋气候&#xff1b;利用高速相机模拟人脑上亿个神经元之间联接与工作&#x…

善康医药冲刺科创板上市:计划募资13亿元,上半年亏损5000万元

近日&#xff0c;深圳善康医药科技股份有限公司&#xff08;下称“善康医药”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺上市&#xff0c;善康医药计划募资13.27亿元&#xff0c;将用于新药研发项目、创新药高端制剂生产基地建设项目、营销…

redis学习第二天

Redis事务 Redis事务是一个单独的隔离操作&#xff1a;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令插队。 Redis事务的主要作用就是串联多个命令防止别的命令插队。 Multi、Exec、discard 从输入Multi命令开始、…

【c++基础】第六章 STL标准模板库

第六章 STL标准模板库认识STLstring字符容器库vector容器迭代器与空间配置器deque容器容器适配置list容器set容器map容器观察者设计模型认识STL STL的概述 STL采用泛型思想&#xff0c;把C中所用到的所有的数据结构&#xff0c;按照一定的标准&#xff0c;全部封装成了一个个…

虚拟机docker网络问题处理

问题 我们有2台设备&#xff0c;ip 为 172.20.30.1 172.20.30.2 &#xff0c;虚拟机上的服务需要连接这2台设备&#xff0c;网络已经做通了&#xff0c;可以正常连接虚拟机异常关闭&#xff0c;重新开启后。发现服务有些问题&#xff0c;就打算将docker服务重新部署&#xff0…

Vue后台项目的记录 (一)

一、下载模板 简洁版: https://github.com/PanJiaChen/vue-admin-template 下载下来的文件介绍 build ----index.js webpack配置文件【很少修改这个文件】 mock ----mock数据的文件夹【模拟一些假的数据mockjs实现的】&#xff0c;因为咱们实际开发的时候&#xff0c;利用的是…

STM32——FATFS文件系统

可裁剪意味着可以选择部分功能&#xff0c;减小占用的空间。 与Windows兼容意味着可以在电脑上直接读取文件。 ①底层接口&#xff0c;包括存储媒介读&#xff0f;写接口&#xff08;disk I/O&#xff09;和供给文件创建修改时间的实时时钟&#xff0c;需要我们根据平台和存储…

深度学习笔记(1)| 导数、偏导数、梯度和方向导数的理解

1. 梯度&#xff08;Gradient&#xff09;的理解 深度学习尝试在权重空间中找到一个方向&#xff0c;沿着该方向能降低损失函数的损失值。其实不需要随机寻找方向&#xff0c;因为可以直接计算出最好的方向&#xff0c;这就是从数学上计算出最陡峭的方向。这个方向就是损失函数…

单页扒手-基于Node的实现

做网站的朋友经常遇见别人的好看的页面&#xff0c;想保存到本地自己用&#xff0c;可是用以前的老办法网页另存为&#xff0c;发现很不好&#xff0c;规则不好处理&#xff0c;路径也不好处理&#xff0c;用这个网页克隆&#xff08;单页模板扒手&#xff09;就很好处理了&…

阿里「杀手锏」级语音识别模型来了!推理效率较传统模型提升10倍,已开源

阿里达摩院&#xff0c;又搞事儿了。 这两天&#xff0c;它们发布了一个全新的语音识别模型&#xff1a; Paraformer。 开发人员直言不讳&#xff1a;这是我们“杀手锏”级的作品。 ——不仅识别准确率“屠榜”几大权威数据集&#xff0c;一路SOTA&#xff0c;推理效率上相比…

Zookeeper 4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作【Watch 事件监听】

Zookeeper 【黑马程序员Zookeeper视频教程&#xff0c;快速入门zookeeper技术】 文章目录Zookeeper4 Zookeeper JavaAPI 操作4.7 Curator API 常用操作4.7.1 Watch 事件监听4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作 4.7.1 Watch 事件监听 【基本概念】 ZooKeep…

异常见闻录-Java.lang.UnsupportedClassVersionError涨知识啦

今天有学生说自己的Eclipse的Java代码只能在新建的项目中运行&#xff0c;运行导入项目中类就报错 异常信息 java.lang.UnsupportedClassVersionError: com/demo1/Demo1Application has been compiled by a more recent version of the Java Runtime (class file version 5…

CMake入门

1. CMake的介绍&#xff1a; 当多人开发同一个项目时&#xff0c;最终要输出一个可执行文件或者共享库&#xff08;dll、so等&#xff09;&#xff0c;就可以使用cmake了 所有操作都是通过 CMakeLists.txt&#xff08;严格区分大小写&#xff09; 来完成的 ----> 简单 2. …

C51——超声波测距 函数封装和舵机代码结合

要注意舵机转动中 延时函数要放在哪里 #include "reg52.h" sbit D5 P3^7;// sbit D6 P3^6;// sbit Trig P1^5; sbit Echo P1^6; sbit sg90_con P1^1; int cnt; int jd; double time; void Delay10us() //11.0592MHz { unsigned ch…

操作系统管程-地址-重定位-内存管理与存储管理

管程的基本概念&#xff1a;为什么会出现管程&#xff1f;信号量机制的不足&#xff1a;程序编写困难、易出错解决&#xff1a;Brinch Hansen(1973)Boare(1974)方案&#xff1a;在程序设计语言中引入管程成分一种高级同步机制管程的定义&#xff1a;是一个特殊的模块有一个名字…

使用MAXScript脚本编写圣诞树建模插件教程

一、前言 2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 今年的圣诞节为大家分享用MAXScript脚本编写圣诞树建模插件的技术创意&#xff0c;喜欢的同学别忘记在下面点个赞&#xff01; 二、创意名 一键圣诞树插件 三、效果展示 四、实现步骤 1.制作圣诞树的设计稿…

智能巡检系统:企业安全生产管理的智能助手

智能巡检是一种高效代替传统人工巡检的新方式&#xff0c;其依靠物联感知技术&#xff0c;通过物联网采集获取信息&#xff0c;自动记录巡检信息&#xff0c;及时发现问题&#xff0c;实现巡检科学化。 工业4.0带来的技术革新加速了企业的转型升级进程&#xff0c;传统企业的运…

F5张振伦:让应用安全、快速、可靠地交付到需要的地方丨2022首届全球数字生态大会

科技云报道原创。 日前&#xff0c;由杭州市人民政府和浙江省商务厅主办的“2022首届全球数字生态大会”在杭州国际博览中心成功举办。 本次大会以“新技术、新业态、新模式”为主题&#xff0c;邀请到国内外20余位演讲嘉宾和超300位专业观众线下参会。 与此同时&#xff0c;…

IB成绩换成GPA,美国大学是如何算的?

IB课程体系是全球公认的难度大、结构强的课程体系。 IB课程可以通过IB文凭课程&#xff08;也称为IBDP&#xff09;进行系统研究。IB课程体系是国际公认的基础文凭&#xff0c;世界上几乎所有大学都认可IB课程体系。这是否意味着IB在申请美国大学时会有优势&#xff1f; 事实上…