程序环境和预处理(1)

文章目录

  • 目录
    • 1. 程序的翻译环境和执行环境
    • 2. 详解编译+链接
      • 2.1 翻译环境
      • 2.2 编译本身也分为几个阶段
      • 2.3 运行环境
    • 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 宏和函数对比

目录

  • 程序的翻译环境
  • 程序的执行环境
  • 详解:C语言程序的编译+链接
  • 预定义符号介绍
  • 预处理指令 #define
  • 宏和函数的对比
  • 预处理操作符#和##的介绍
  • 命令定义
  • 预处理指令 #include
  • 预处理指令 #undef
  • 条件编译

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

在ANSI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。

计算机是能够执行二进制指令的,但是我们写出的C语言代码是文本信息,计算机不能直接理解。

翻译环境:C语言代码 —> 二进制的指令(放在可执行程序中)

执行环境:执行二进制的代码

2. 详解编译+链接

2.1 翻译环境

翻译环境

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

我们也可以通过代码来观察:

//add.c

int Add(int x, int y)
{
	return x + y;
}
//test.c

#include <stdio.h>

extern int Add(int, int);

int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("%d\n", c);

	return 0;
}

目标文件和可执行程序

2.2 编译本身也分为几个阶段

翻译环境详解

  1. 预处理阶段
    预处理阶段
  2. 编译阶段
    编译阶段
  3. 汇编阶段
    汇编阶段

解释一下符号汇总、符号表:
代码
ELF文件格式
readelf结果
在这个目标文件里,确实能看到一些符号(都是全局的


接下来,我们用两个文件来举例子:符号汇总解释

这有什么用呢?

如果没有定义Add函数,那么在链接的时候就定位不到这个函数,就会发生链接错误,生成不了可执行程序。

2.3 运行环境

程序执行的过程:

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

3. 预处理详解

3.1 预定义符号

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

这些预定义符号都是语言内置的。

举个例子:

#include <stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%d\n", __STDC__);//当前VS是不支持ANSI	C

	return 0;
}

对代码进行预处理之后:
预定义符号

3.2 #define

3.2.1 #define 定义标识符

语法:
#define name stuff

#include <stdio.h>

#define M 100
#define STR "abc"
#define FOR for(;;)
#define reg register//为 register这个关键字,创建一个简短的名字

int main()
{
	printf("%d\n", M);
	printf("%s\n", STR);

	FOR;//死循环

	return 0;
}

预处理之后:
#define定义标识符


int main()
{
	int d = 0;

	switch (d)
	{
	case 1:
		break;
	case 2:
		break;
	case 3:
		break;
	}

	return 0;
}

以上代码还可以这样写:

#define CASE break;case

int main()
{
	int d = 0;

	switch (d)
	{
	case 1:
	CASE 2:
	CASE 3:
	}

	return 0;
}

#include <stdio.h>

//如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\tdate:%s\t\
					time:%s\n" , __FILE__, __LINE__,\
					__DATE__, __TIME__)

int main()
{
	DEBUG_PRINT;

	return 0;
}

提问:

在define定义标识符的时候,要不要在最后加上 ;

比如:

#define M 100;

int main()
{
	int a = M;

	return 0;
}

预处理(1)
这样的代码看上去是没有问题的,但是这样写是非常容易出错的:

#define M 100;

int main()
{
	int a = 0;
	int b = 0;

	if (a > 5)
		b = M;
	else
		b = -1;

	return 0;
}

预处理(2)
if 语句后面默认只能跟一条语句,这里再加上一个 ; 就变成了两条语句,这就意味着下面的 else 不知道和谁匹配了。

因此,建议不要加上 ; ,这样容易导致问题。

3.2.2 #define 定义宏

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

下面是宏的申明方式:

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

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

#include <stdio.h>

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 2;
	int b = -2;
	int c = MAX(a, b);
	printf("c=%d\n", c);

	return 0;
}

预处理之后:
预处理(3)
提示:

所有用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间操作符的优先级而导致不可预料的相互作用。

预处理(4)
应该这样写:

#include<stdio.h>

#define SQUARE(x) ((x) * (x))

int main()
{
	int a = 3;
	int r = SQUARE(a + 2);
	printf("r=%d\n", r);

	return 0;
}

预处理(5)
应该这样写:

#include<stdio.h>

#define DOUBLE(x) ((x) + (x))

int main()
{
	int a = 3;
	int r = 10 * DOUBLE(a);
	printf("r=%d\n", r);

	return 0;
}
3.2.3 #define 替换规则

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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

预处理(6)
注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#include<stdio.h>

#define M 3

int main()
{
	printf("M=%d\n", M);//M=3

	return 0;
}
3.2.4 #和##

如何把参数插入到字符串中?

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

#include <stdio.h>

int main()
{
	printf("hello world\n");//hello world
	printf("hello ""world\n");//hello world

	return 0;
}

我们发现字符串是有自动连接的特点的。


我们想实现这样一个功能:

#include <stdio.h>

int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	
	int b = 15;
	printf("the value of b is %d\n", b);

	float f = 4.5f;
	printf("the value of f is %f\n", f);

	return 0;
}

我们可以用宏来实现(函数做不到):

#include <stdio.h>

#define PRINT(n, format) printf("the value of "#n" is "format"\n", n)

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

	int b = 15;
	//printf("the value of b is %d\n", b);
	PRINT(b,"%d");

	float f = 4.5f;
	//printf("the value of f is %f\n", f);
	PRINT(f, "%f");

	return 0;
}

代码中的 #n预处理“n”


##的作用:

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

#include <stdio.h>

#define CAT(x,y) x##y

int main()
{
	int Class110 = 2024;
	printf("%d\n", CAT(Class, 110));//2024
	printf("%d\n", Class110);//2024

	return 0;
}

预处理(7)

3.2.5 带副作用的宏参数

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

int main()
{
	int a = 10;
	//int b = a + 1;//b=11, a=10
	int b = ++a;//b=11, a=11

	return 0;
}

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题:

#include <stdio.h>

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

int main()
{
	int a = 5;
	int b = 6;
	int c = MAX(a++, b++);
	//int c = ((a++) > (b++) ? (a++) : (b++));
	//        5       6               7
	//c=7
	//b=8
	//a=6

	printf("c = %d\n", c);
	printf("a = %d\n", a);
	printf("b = %d\n", b);

	return 0;
}
3.2.6 宏和函数对比
#include <stdio.h>

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

//2
int Max(int x, int y)
{
	return (x > y ? x : y);
}

int main()
{
	int a = 5;
	int b = 6;
	int c = MAX(a, b);
	//int c = Max(a, b);

	printf("c = %d\n", c);
	printf("a = %d\n", a);
	printf("b = %d\n", b);

	return 0;
}

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

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

原因有二:

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

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

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

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。(代码如果特别长,那么编译的压力就会很大,因为在编译的时候会对代码做各种各样的处理,如:语法分析、词法分析等等)
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

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

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

int main()
{
	int* p = (int*)malloc(126 * sizeof(int));

	//malloc(126, int);//err

	int* p = MALLOC(126, int);
	//int* p = (int*)malloc(126 * sizeof(int));

	return 0;
}

宏和函数的一个对比
宏和函数的区别

对于第三点的一个例子:

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

//2
int Max(int x, int y)
{         //5      6
	return (x > y ? x : y);
}

int main()
{
	int c = MAX(2 + 3, 6);
	//int c = ((2+3)>(6)?(2+3):(6))
	c = Max(2 + 3, 6);

	return 0;
}

补充:
宏有自己的优势,当然也有劣势

函数也有自己的优势,也有劣势

能不能有一个函数既具有函数的好,也具有宏的好呢?

inline — 内联

内联函数

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

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

相关文章

HAT论文详解:Activating More Pixels in Image Super-Resolution Transformer

code&#xff1a;https://github.com/XPixelGroup/HAT paper: https://arxiv.org/abs/2309.05239 1. 概述 本文是对Swinir的改进&#xff0c;目前很多图像超分Benchmark的SOTA。相对于SwinIR的改进主要有三个地方&#xff1a;1. 引入Channel Attention,以获得更好的全局能力&…

【Linux】 login命令使用

login命令 在 Linux 中用于创建一个新的会话&#xff0c;并在新会话中登录用户。这个命令通常在终端中自动执行&#xff0c;当你打开一个新的终端会话或者通过 SSH 远程连接到 Linux 服务器时。 在命令后面附加欲登入的用户名称&#xff0c;它会直接询问密码&#xff0c;等待…

探索水下低光照图像检测性能,基于YOLOv3全系列【yolov3tiny/yolov3/yolov3spp】参数模型开发构建海底生物检测识别分析系统

海底这类特殊数据场景下的检测模型开发相对来说比较少&#xff0c;在前面的博文中也有一些涉及&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《尝试探索水下目标检测&#xff0c;基于yolov5轻量级系列模型n/s/m开发构建海底生物检测系统》 《基于YOLOv5C3CBAMCBA…

Element table 实现表格行、列拖拽功能

安装包 npm install sortablejs --save <template><div class"draggable" style"padding: 20px"><el-table row-key"id" :data"tableData" style"width: 100%" border><el-table-columnv-for"(it…

【Linux】 faillock 命令使用

faillock 命令 faillock 命令是 PAM (Pluggable Authentication Modules) 的一部分&#xff0c;它被设计用来跟踪失败的登录尝试&#xff0c;并在连续失败尝试超过某个阈值时锁定账户。这个功能可以帮助系统管理员识别和防止暴力破解攻击。当一个用户连续多次输入错误的密码后&…

如何开发自己的npm包并上传到npm官网可以下载

目录 搭建文件结构 开始编写 发布到npm 如何下载我们发布的npm包 搭建文件结构 先创建新文件夹,按照下面的样子布局 .├── README.md //说明文档 ├── index.js //主入口 ├── lib //功能文件 └── tests //测试用例 然后再此根目录下初始化package包 npm init…

Cartographer 构建约束优化问题

计算优化的思路&#xff0c;需要两种坐标变换&#xff0c;用两种坐标变化的残差求解。 第一种残差 将节点与子图原点在global坐标系下的相对位姿与约束的差值作为残差项 第一种坐标变换&#xff1a;节点与子图原点在global坐标系下的坐标变换 第二种坐标变换&#xff1a;子图内…

NXP实战笔记(八):S32K3xx基于RTD-SDK在S32DS上配置LCU实现ABZ解码

目录 1、概述 2、SDK配置 2.1、IO配置 2.2、TRGMUX配置 2.3、LCU配置 2.4、Trgmux配置 2.5、Emios配置 2.6、代码实现 1、概述 碰到光电编码器、磁编码器等,有时候传出来的位置信息为ABZ的方式,在S32K3里面通过TRGMUX、LCU、Emios结合的方式可以实现ABZ解码。 官方…

【Pytorch】从MoCo看无监督对比学习;从SupCon看有监督对比学习

目录 无监督对比学习&#xff1a;Moco文章内容理解代码解释 有监督对比学习&#xff1a;Supervised Contrastive Learning文章内容理解 无监督对比学习&#xff1a;Moco 文章内容理解 以下内容全部来自于&#xff1a;自监督学习-MoCo-论文笔记. 侵删 论文&#xff1a;Momentu…

vue : 无法加载文件 C:\Program Files\nodejs\node_global\vue.ps1,因为在此系统上禁止运行脚本。

解决方法&#xff1a; 打开PowerShell&#xff0c;在命令框输入set-ExecutionPolicy RemoteSigned 在PowerShell中输入会出现如下图&#xff0c;输入y即可。

【MATLAB】 RLMD信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 RLMD分解算法 RLMD&#xff08;Robust Local Mode Decomposition&#xff09;是一种鲁棒的局部模态分解方法。它是通过在局部区间内对信号进行多项式拟合&#xff0c;提取局部特征&#xff0c;进而分解信…

【Vuforia+Unity】AR07-实现识别条码、二维码内容功能(Barcode Scanner)

Barcode Scanner in Unity | Vuforia Library官方教程,写的很详细,本教程主要参考对象! 主要实现扫描生活中常见的二维码,然后弹出二维码链接,当然我们也可以再次回调自定义函数,弹出数字内容,AR内容效果! 支持的二维码: 局限性 条码扫描组件还定义检测和跟踪的条形…

2024年2月前端资讯动态:JSR新仓库革新及Set方法等全新特性

2024年2月前端技术领域再次迎来了一系列激动人心的更新和进展。无论是新兴的包仓库JSR&#xff0c;还是JavaScript提案中的Set方法、Array.prototype.with()的加入&#xff0c;都预示着前端开发的未来将更加灵活和强大。本文将为您详细介绍这些技术的最新动态&#xff0c;帮助您…

【电路笔记】-RC放电电路

RC放电电路 文章目录 RC放电电路1、概述2、RC放电电路3、RC放电电路示例当电压源从完全充电的 RC 电路中移除时,电容器 C 将通过电阻 R 放电。 1、概述 RC 放电电路利用电阻器-电容器组合的固有 RC 时间常数以指数衰减率对电容器进行放电。 在之前的 RC 充电电路教程中,我们…

挑战30天学完Python:Day18 正则表达式

&#x1f4d8; Day 18 &#x1f389; 本系列为Python基础学习&#xff0c;原稿来源于 30-Days-Of-Python 英文项目&#xff0c;大奇主要是对其本地化翻译、逐条验证和补充&#xff0c;想通过30天完成正儿八经的系统化实践。此系列适合零基础同学&#xff0c;或仅了解Python一点…

【区块链】联盟链

区块链中的联盟链 写在最前面**FAQs** 联盟链&#xff1a;区块链技术的新兴力量**联盟链的定义****联盟链的技术架构**共识机制智能合约加密技术身份认证 **联盟链的特点**高效性安全性可控性隐私保护 **联盟链的应用场景****金融服务****供应链管理****身份验证****跨境支付**…

2024云服务器ECS_云主机_服务器托管_e实例-阿里云

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如ECS经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云服务器网al…

【风格迁移】CAST:对比学习,从图像特征而非其二阶统计量(Gram矩阵)中学习风格

CAST&#xff1a;对比学习&#xff0c;从图像特征而非其二阶统计量&#xff08;Gram矩阵&#xff09;中学习风格 提出背景5 why 分析5 so分析 CAST 框架多层风格投影器领域增强模块生成网络 效果对比 StyleGAN 提出背景 论文&#xff1a;https://arxiv.org/pdf/2205.09542.pdf…

现货白银交易时间笔记

现货白银是效率和收益率“双高”的投资工具&#xff0c;但对于不了解这个品种的投资者来说&#xff0c;在正式展开交易之前&#xff0c;可能需要先经历一个学习的过程&#xff0c;才能全面地了解它的特性&#xff0c;而了解过程往往是从它的交易时间开始。 现货白银实现24小时交…

Django学习笔记-forms使用

1.创建forms.py文件,导入包 from django import forms from django.forms import fields from django.forms import widgets2. 创建EmployeeForm,继承forms.Form 3.创建testform.html文件 4.urls.py添加路由 5.views中导入forms 创建testform,编写代码 1).如果请求方式为GET,…