程序环境和预处理(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.cint 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	Creturn 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;caseint 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;elseb = -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 3int main()
{printf("M=%d\n", M);//M=3return 0;
}
3.2.4 #和##

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

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

#include <stdio.h>int main()
{printf("hello world\n");//hello worldprintf("hello ""world\n");//hello worldreturn 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##yint main()
{int Class110 = 2024;printf("%d\n", CAT(Class, 110));//2024printf("%d\n", Class110);//2024return 0;
}

预处理(7)

3.2.5 带副作用的宏参数

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

int main()
{int a = 10;//int b = a + 1;//b=11, a=10int b = ++a;//b=11, a=11return 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=6printf("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);//errint* 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      6return (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 — 内联

内联函数

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

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

相关文章

数据结构链表力扣例题AC(3)——代码以及思路记录

160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 AC写法一 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {//思…

DBAPI如何使用数组类型参数

DBAPI如何使用数组类型参数 需求 根据多个id去查询学生信息 API创建 在基本信息标签&#xff0c;创建参数ids &#xff0c;参数类型选择 Array<bigint> 在执行器标签&#xff0c;填写sql&#xff0c;使用in查询 select * from student where id in <foreach ope…

推荐系统经典模型YouTubeDNN

文章目录 YouTubeDNN概念YouTubeDNN模型架构图YouTubeDNN召回阶段YouTubeDNN层级介绍 YouTubeDNN排序阶段YoutubeDNN模型中的一些Trick负采样问题特征构造上下文选择 总结 YouTubeDNN概念 YouTubeDNN是YouTube用于做视频推荐的落地模型&#xff0c;其大体思路就是召回阶段使用…

33.云原生之Istio管理任何七层流量

云原生专栏大纲 文章目录 Istio存在的问题Aeraki介绍Aeraki 的解决方案支持的协议支持的特性 安装AerakiAeraki教程采用 ServiceEntry 的 Demo 应用使用 Dubbo2Istio 对接 Dubbo 注册表 的 Demo 应用&#xff08;Interface 级流量治理&#xff09; Service Mesh 中有大量的七层…

2024年全国乙卷高考文科数学备考:历年选择题真题练一练(2014~2023)

今天距离2024年高考还有三个多月的时间&#xff0c;今天我们来看一下2014~2023年全国乙卷高考文科数学的选择题&#xff0c;从过去十年的真题中随机抽取5道题&#xff0c;并且提供解析。后附六分成长独家制作的在线练习集&#xff0c;科学、高效地反复刷这些真题&#xff0c;吃…

【C语言】linux内核ipoib模块 - ipoib_ib_post_receive

一、中文注释 用于以太网接口&#xff08;InfiniBand&#xff09;上的IP over IB&#xff08;IPoIB&#xff09;设备的Linux内核函数&#xff0c;负责将接收缓冲区&#xff08;一个包&#xff09;提交到网络设备的队列中等待数据到达。下面是中文注释版本的函数代码&#xff1…

国家建筑装配式内装产业基地在沪成立,副主任单位优积科技协同助推绿色低碳循环发展

上海市室内装饰行业协会装配式内装产业专业委员会成立大会暨“国家建筑装配式内装产业基地”项目启动会于3月21日下午1点在上海光大酒店隆重举行。出席此次活动的包括市装协会长徐国俭&#xff0c;市装协党支部书记兼秘书长丛国梁&#xff0c;市装协装配式内装委主任顾泰昌&…

内容安全补充

第十一天 密码学 近现代加密算法 古典加密技术 --- 算法保密原则 近现代加密技术 --- 算法公开&#xff0c;密钥保密 对称加密算法&#xff0c;非对称加密算法 对称加密 --- 加密和解密的过程中使用的是同一把密钥。 所以&#xff0c;对称加密所使用的算法一定是一种双向…

Node.js+vue校内二手物品交易系统tdv06-vscode前后端分离

二手物品交易系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的nodejs进行编写&#xff0c;使用了vue框架。该系统从三个对象&#xff1a;由管理员和用户、店铺来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户、店铺、二…

【RN】为项目使用React Navigation中的navigator

简言 移动应用基本不会只由一个页面组成。管理多个页面的呈现、跳转的组件就是我们通常所说的导航器&#xff08;navigator&#xff09;。 React Navigation 提供了简单易用的跨平台导航方案&#xff0c;在 iOS 和 Android 上都可以进行翻页式、tab 选项卡式和抽屉式的导航布局…

如何在 Tomcat 中为 Web 应用程序启用和配置缓存?

在Tomcat中为Web应用程序启用和配置缓存通常涉及到对Tomcat的连接器&#xff08;Connector&#xff09;进行配置&#xff0c;以及可能的话&#xff0c;配置Web应用程序本身以支持缓存。 1. 配置Tomcat连接器以启用缓存 Tomcat的连接器可以通过其配置来启用各种…

软考-中级-系统集成2023年综合知识(三)

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 软考中级专栏回顾 专栏…

C#学习总结

1、访问权限 方法默认访问修饰符&#xff1a;private 类默认访问修饰符&#xff1a;internal 类的成员默认访问修饰符&#xff1a;private 2、UserControl的使用 首先添加用户控件 使用时一种是通过代码添加&#xff0c;一种是通过拖动组件到xaml中

Stable Diffusion 绘画入门教程(webui)-ControlNet(IP2P)

上篇文章介绍了深度Depth&#xff0c;这篇文章介绍下IP2P&#xff08;InstructP2P&#xff09;, 通俗理解就是图生图&#xff0c;给原有图加一些效果,比如下图&#xff0c;左边为原图&#xff0c;右边为增加了效果的图&#xff1a; 文章目录 一、选大模型二、写提示词三、基础参…

RabbitMQ的死信队列和延迟队列

文章目录 死信队列如何配置死信队列死信队列的应用场景Spring Boot实现RabbitMQ的死信队列 延迟队列方案优劣&#xff1a;延迟队列的实现有两种方式&#xff1a; 死信队列 1&#xff09;“死信”是RabbitMQ中的一种消息机制。 2&#xff09;消息变成死信&#xff0c;可能是由于…

绿盾限制终端网络访问权限会恢复后,别的网站访问正常就是无法访问钉钉网站和下载东西

环境&#xff1a; Win10 专业版 钉钉7.5.5 绿盾7.0 问题描述&#xff1a; 绿盾限制终端网络访问权限会恢复后&#xff0c;别的网站访问正常就是无法访问钉钉网站和下载东西 解决方案&#xff1a; 排查方法 1.重置浏览器或者更换浏览器测试&#xff08;未解决&#xff09…

【Java】输入输出流(实验八)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 1、掌握java I/O的基本原理。 2、掌握标准输入输出流和Scanner类的基本使用方法。 3、掌握FileInputStream、FileOutStream、FileReader、FileWriter、BufferedReader 、BufferedWriter类的常用方法。 二、实验…

VR系统的开发流程

虚拟现实&#xff08;Virtual Reality&#xff0c;VR&#xff09;系统是一种通过计算机技术模拟出的具有三维视角和交互性的虚拟环境&#xff0c;使用户能够沉浸在其中并与虚拟环境进行交互。这种技术通常利用头戴式显示器和手柄等设备&#xff0c;使用户能够感觉到仿佛身临其境…

【kubernetes】kubeadm部署k8s集群(3主3从+keepalived/nginx负载均衡高可用)

目录 一、完成系统初始化 步骤一&#xff1a;常规环境初始化 步骤二&#xff1a;内核版本升级以及内核限制文件参数修改 步骤三&#xff1a;提前准备好负载均衡器和keepalived(接着之前的二进制部署修改的) 二、所有节点部署docker&#xff0c;以及指定版本的kubeadm 步骤…

Mysql系列之命令行登录、连接工具登录、数据库表常用命令

登录与常用命令 连接工具登录命令行登录数据库1、查看数据库2、指定数据库3、查看当前数据库4、建库语句 数据表1、查看数据表2、查看表结构信息3、查看建表语句4、建表语句 连接工具登录 首先下载mysql连接工具&#xff0c;解压后直接打开软件&#xff0c;按以下步骤操作&…