程序环境和预处理

导言:

在编写代码的过程中,我们一般都是在一些图形化软件的编译器中实现,编译器帮我们实现了很多操作,这里就一些简单的过程进行说明。本文主要阐述了c语言程序的编译链接以及一些预处理知识,和宏定义的使用。

目录

导言:

正文:

一.编译和链接

二.预处理

三.宏定义

四.条件编译

总结:


正文:

C语言程序经过以下几个步骤才能转换成可执行程序:

  1. 预处理(Preprocessing):预处理器将源代码中的预处理指令(以#开头的指令)进行处理,如宏定义展开、头文件包含等。预处理的结果是一个经过宏展开和头文件替换的新的源代码文件。

  2. 编译(Compiling):编译器将预处理后的源代码文件翻译成汇编代码。汇编代码是一种低级的表示形式,使用汇编语言描述了程序的指令和数据。

  3. 汇编(Assembling):汇编器将汇编代码转换成机器码,即可执行文件的一部分。汇编器将汇编语言指令翻译成机器指令,并生成与机器硬件平台相关的目标文件。

  4. 链接(Linking):链接器将多个目标文件以及库文件(如C运行时库)合并成一个可执行文件。链接器会解析目标文件中的符号引用,并将其与符号定义进行匹配,生成最终的可执行文件。

  5. 加载(Loading):操作系统将可执行文件加载到内存中,并为其分配资源,如内存空间、文件句柄等。加载完成后,程序开始执行。

  6. 执行(Execution):程序按照指令序列执行,从程序入口开始,依次执行各个指令。程序执行过程中可能会读取和修改内存中的数据,进行函数调用、条件判断、循环等操作。

一.编译和链接

编译链接是将源代码转换成可执行程序的过程。在C语言中,编译器负责将源代码翻译成汇编代码,链接器将汇编代码和库文件组合起来形成可执行程序。

编译过程是将高级语言(如C语言)源代码转换为机器语言的过程。它通常包括以下几个步骤:

1. 词法分析(Lexical Analysis):词法分析器将源代码分解为词法单元(Token),如关键字、标识符、运算符、常量等。它通过扫描源代码字符流,根据预定义的词法规则进行匹配和分解。

2. 语法分析(Syntax Analysis):语法分析器根据语法规则,将词法单元组合成语法结构,如语句、表达式、函数等。它通过构建语法树(Syntax Tree)来表示程序的结构和语义。

3. 语义分析(Semantic Analysis):语义分析器对语法树进行语义检查,确保程序的语义正确性。它会进行类型检查、符号表管理、作用域分析等操作,以识别和纠正潜在的语义错误。

4. 中间代码生成(Intermediate Code Generation):中间代码生成器将语法树转换为中间代码,如三地址码、虚拟机代码等。中间代码是一种抽象的表示形式,它简化了后续的优化和目标代码生成过程。

5. 代码优化(Code Optimization):代码优化器对中间代码进行优化,以改善程序的性能和效率。它会进行常量折叠、循环展开、公共子表达式消除等优化操作,以减少执行时间和内存消耗。

6. 目标代码生成(Code Generation):目标代码生成器将优化后的中间代码转换为目标机器代码。它会根据目标机器的特性和指令集,生成与之对应的机器指令序列。

7. 符号解析和地址分配(Symbol Resolution and Address Allocation):符号解析器解析程序中的符号引用,将其与符号定义进行匹配。地址分配器为程序中的变量和数据分配内存空间,生成相应的地址和偏移量。

8. 目标代码链接(Code Linking):目标代码链接器将多个目标文件和库文件链接在一起,生成最终的可执行文件。它会解析目标文件中的符号引用和定义,进行符号重定位,以解决符号的地址和位置问题。

以上是一般的编译过程,不同的编译器和编程语言可能会有细微的差异。编译过程中的每个步骤都有其特定的功能和作用,通过这些步骤的协同工作,将源代码转换为可执行程序。

在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实现代码。

翻译环境:

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

运行环境:

运行环境是指程序在运行时所依赖的硬件和软件环境。它包括操作系统、处理器架构、内存、硬盘、网络等硬件设备,以及操作系统、编程语言的运行时库、第三方库等软件组件。运行环境为程序提供了运行所需的资源和支持,确保程序能够正常执行。

程序执行的过程:

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

二.预处理

预处理是编译过程的第一步,它是在实际的编译之前对源代码进行处理的过程。预处理器会根据预定义的指令和规则,对源代码进行一系列的替换和操作,生成经过预处理的代码。预处理的主要功能包括:文件包含,宏替换,条件编译,符号定义,注释处理。

  1. 文件包含(File Inclusion):预处理器会处理源代码中的#include指令,将被包含的文件内容插入到指令所在的位置。这样可以将多个源文件合并为一个文件,方便管理和复用代码。
  2. 宏替换(Macro Substitution):预处理器会处理源代码中的宏定义,将宏名称替换为其定义的内容。宏定义可以是简单的文本替换,也可以包含参数和复杂的表达式。宏替换可以减少代码重复,提高代码的可读性和维护性。
  3. 条件编译(Conditional Compilation):预处理器会处理源代码中的条件编译指令,根据指令的条件判断结果决定是否编译某段代码。条件编译可以根据不同的编译选项和平台要求,选择性地编译和执行特定的代码块。
  4. 符号定义(Symbol Definition):预处理器可以定义符号,如宏定义、常量定义等。这些符号可以在源代码中使用,用于控制编译过程和代码的行为。
  5. 注释处理(Comment Processing):预处理器会处理源代码中的注释,将其删除或替换为空格。注释对于代码的可读性和注释文档的生成非常重要。

预处理器会根据源代码中的预处理指令和规则,对代码进行处理,并生成经过预处理的代码。预处理后的代码会成为编译器的输入,进一步进行词法分析、语法分析和语义分析等步骤。预处理阶段的主要目的是对源代码进行预处理,为后续的编译过程做准备。

三.宏定义

在C语言中,宏定义是一种预处理指令,用于将一个标识符或表达式替换为指定的文本。宏定义可以简化代码,提高代码的可读性和维护性。

1.宏定义的语法:

宏定义使用#define关键字进行定义,语法格式如下:

#define 宏名 替换文本

宏名是一个标识符,用于表示一个宏。替换文本是一个文本字符串,可以是一个表达式、语句或任意的文本。

例如:

#define MAX 1000

2.宏定义的替换规则:

当预处理器遇到一个宏调用时,会将宏名替换为宏定义中的替换文本。替换是简单的文本替换,没有类型检查和语法分析。替换文本中的参数可以使用###进行特殊处理。

3.宏定义的参数:

宏定义可以包含参数,用于在宏调用时传递参数。参数使用圆括号括起来,可以有多个参数,用逗号分隔。参数可以在替换文本中使用,并通过###进行特殊处理。

4.宏定义的特殊处理:

  • #操作符:在宏定义中,#操作符用于将参数转换为字符串。在替换文本中,使用#操作符将参数转换为字符串字面量。
  • ##操作符:在宏定义中,##操作符用于将两个参数进行连接。在替换文本中,使用##操作符将两个参数连接在一起。

先看一段代码:

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

 运行结果如下:

这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。
但是使用# ,把一个宏参数变成对应的字符串。例如:

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

运行如下:

 

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

#include<stdio.h>
#define ADD_TO_SUM(num, value) sum##num += value
int main() {int sum5 = 0;printf("%d", ADD_TO_SUM(5, 10));作用是:给sum5增加10.return 0;
}

 运行结果:

5.宏定义的注意事项:

  • 宏定义没有作用域限制,它在定义的位置之后的代码中都可以使用。
  • 宏定义是简单的文本替换,没有类型检查和语法分析。因此,在使用宏定义时要注意替换的文本是否符合语法规则。
  • 宏定义可以嵌套使用,但要注意嵌套的次数和顺序,以避免出现意外的替换结果。
  • 使用宏定义时要注意替换的文本是否会引起歧义或产生副作用,避免出现意外的行为。

注意在define定义标识符的时候,在最后最好不要加上分号‘ ; ',这样容易导致错误。 例如:

define MAX 1000;
if(condition)
max = MAX;
else
max = 0;

这样会出现语法错误,因为宏定义是简单的文本替换,所以MAX会被替换成1000;所以会多出一个分号,而if后只能够有一条语句,所以会报错。

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

#define name( parament-list ) stuff


其中的parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

同时,也还要注意宏使用时的运算顺序,由于宏定义是简单的文本替换所以可能造成运算顺序的混乱。例如:

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

乍一看,你可能觉得这段代码将打印36这个值。
事实上,它将打印11.因为替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );如果我们想要避免这种问题可以使用括号将x作为一个整体看成一个表达式。例如:

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

这样结果就是:36  

四.条件编译

条件编译(Conditional Compilation)是一种在编译时根据条件选择性地包含或排除代码的技术。它可以根据不同的条件,编译不同的代码或定义不同的符号。条件编译通常用于实现平台特定的代码、调试代码、特定功能的开关等。例如调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。条件编译主要通过预处理指令 #if#ifdef#ifndef#elif 和 #endif 来实现。

1.#if:用于判断一个常量表达式是否为真,如果为真,则编译 #if 和 #endif 之间的代码。语法格式如下:

#if 常量表达式// 代码块
#endif

2.#ifdef:用于判断一个符号是否已经定义,如果已经定义,则编译 #ifdef 和 #endif 之间的代码。语法格式如下:

#ifdef 符号// 代码块
#endif

3.#ifndef:用于判断一个符号是否未定义,如果未定义,则编译 #ifndef 和 #endif 之间的代码。语法格式如下:

#ifndef 符号// 代码块
#endif

4.#elif:用于在多个条件之间进行选择,如果前面的条件不满足,则判断下一个条件是否为真。语法格式如下:

#if 常量表达式1// 代码块1
#elif 常量表达式2// 代码块2
#elif 常量表达式3// 代码块3
#else// 代码块4
#endif

条件编译可以根据不同的条件选择性地编译代码,这样可以实现平台特定的代码、调试代码、特定功能的开关等。

总结:

编译链接是软件开发过程中不可或缺的环节,它使得我们能够将高级语言编写的代码转换成机器语言,从而实现程序的功能。合理使用宏定义可以简化代码,提高代码的可读性和维护性。但是,过度使用宏定义可能会导致代码的可读性下降,产生难以调试和维护的代码。因此,在使用宏定义时要慎重考虑,遵循良好的编码规范。

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

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

相关文章

IDEA提高工作效率的实用技巧

IDEA是一款备受开发者喜爱的集成开发环境&#xff0c;它提供了许多实用的功能&#xff0c;可以帮助我们更快速、更高效地编写代码。本文将介绍一些IDEA的使用技巧提高工作效率的实用技巧。 验证正则表达式 要验证编写的正则表达式是否正确&#xff0c;只需将光标放在要检查的…

MongoDB 未授权访问漏洞

简介 MongoDB是一个基于分布式文件存储的数据库&#xff0c;是一个介于关系数据库和非关系数据库之间的产品&#xff0c;它的特点是高性能、易部署、易使用&#xff0c;存储数据非常方便&#xff0c;默认情况下是没有认证的这就导致不熟悉它的研发人员部署后没有做访问控制导致…

C++项目实战——基于多设计模式下的同步异步日志系统-⑩-异步缓冲区类与异步工作器类设计

文章目录 专栏导读异步缓冲区设计思想异步缓冲区类设计异步工作器类设计异步日志器设计异步缓冲区类整理异步工作器类整理 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师&#xff0c;阿…

LeetCode算法栈—有效的括号

目录 有效的括号 用到的数据结构&#xff1a; 位运算、Map 和 Stack Stack 常用的函数&#xff1a; 题解&#xff1a; 代码&#xff1a; 运行结果; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符…

模拟IIC通讯协议(stm32)(硬件iic后面在补)

一、IIC基础知识总结。 1、IIC通讯需要两条线就可以&#xff0c;SCL、SDA。 2、IIC的数据传输的速率&#xff0c;不同的ic是不同的&#xff0c;根据电平维持的延时函数的时间来确定IIC数据传输的速率. 3、IIC的延时函数可以使用延时函数&#xff0c;延时函数一般使用系统滴答时…

20款VS Code实用插件推荐

前言&#xff1a; VS Code是一个轻量级但功能强大的源代码编辑器&#xff0c;轻量级指的是下载下来的VS Code其实就是一个简单的编辑器&#xff0c;强大指的是支持多种语言的环境插件拓展&#xff0c;也正是因为这种支持插件式安装环境开发让VS Code成为了开发语言工具中的霸主…

Fast DDS之Subscriber

目录 SubscriberSubscriberQosSubscriberListener创建Subscriber DataReaderSampleInfo读取数据 Subscriber扮演容器的角色&#xff0c;里面可以有很多DataReaders&#xff0c;它们使用Subscriber的同一份SubscriberQos配置。Subscriber可以承载不同Topic和数据类型的DataReade…

【QT】QTreeWidget

新建项目 第一步&#xff1a;设置头标签 第二步&#xff1a;设置item 第三步&#xff1a;创建子item&#xff0c;挂载在顶层item下 完整代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::W…

C++项目——云备份-①-项目介绍环境搭建

文章目录 专栏导读1.什么是云备份2.实现目标3.服务端程序负责功能4.服务端功能模块划分5.客户端程序负责功能6.客户端功能模块划分开发环境环境搭建1. gcc 升级7.3版本2.安装 jsoncpp 库3.下载bundle数据压缩库4.下载 httplib 库 专栏导读 &#x1f338;作者简介&#xff1a;花…

babel6使用ES2020最新js语法

babel6使用ES2020最新js语法 Babel 6 原本是不支持 ES2020 语法&#xff0c;因为它是在 Babel 7 中引入的。如果您想使用 ES2020 语法&#xff0c;您需要将 Babel 6 升级到 Babel 7 或更高版本(推荐),当然也可以在bebel6中安装支持某个语法的plugin,比如你想使用 ES2020 中的可…

Linux程序调试器——gdb的使用

gdb的概述 GDB 全称“GNU symbolic debugger”&#xff0c;从名称上不难看出&#xff0c;它诞生于 GNU 计划&#xff08;同时诞生的还有 GCC、Emacs 等&#xff09;&#xff0c;是 Linux 下常用的程序调试器。发展至今&#xff0c;GDB 已经迭代了诸多个版本&#xff0c;当下的…

完美解决 在将最终稿件上传到 IEEE PDF eXpress进行格式检查是出现“font not embedded“的问题 (不会出现自动压缩图像的现象)

最近中了一篇IEEE的论文&#xff0c;在校稿阶段&#xff0c;final paper是需要通过IEEE PDF eXpress网站的格式检查&#xff0c;然后出现一下问题&#xff1a; Errors: Font TimesNewRomanPS-BoldMT, TimesNewRomanPS-ItalicMT, TimesNewRomanPSMT is not embedded 用人话说就…

模式植物GO背景基因集制作

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 写在前面 关于GO背景基因集文件的制作&#xff0c;我们在很早以前也发过。近两天&#xff0c;自己在分析时候&#xff0c;也是被搞了头疼。想重新制作一份GO背景基因集&#xff0c;进行富集分析。但是结果&…

JAVAEE初阶相关内容第十五弹--网络編程

写在前 简单描述一下关于路由器的三层转发和交换机的二层转发。 路由器是三层转发-->在网络层转发。【需要解析出IP协议中的源IP、目的IP来规划路径】 交换机是二层转发-->在数据链路层转发。【只需要关注下一步发展到哪个相邻的设备上&#xff0c;不需要IP地址&#…

JAVA生成ORC格式文件

一、背景 由于需要用到用java生成hdfs文件并上传到指定目录中&#xff0c;在Hive中即可查询到数据&#xff0c;基于此背景&#xff0c;开发此工具类 ORC官方网站&#xff1a;https://orc.apache.org/ 二、支持数据类型 三、工具开发 package com.xx.util;import com.alibab…

【计算机网络笔记】分组交换中的报文交付时间计算例题

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 系列文章目录题目解答 题目 在下图所示的采用“存储-转发”方式的分组交换网络中所有链路的数据传输速率为100 Mbps&#xff0c;分…

node+vue+mysql后台管理系统

千千博客系统&#xff0c;该项目作为一套多功能的后台框架模板&#xff0c;适用于绝大部分的后台管理系统开发。基于 vue.js&#xff0c;使用 vue-cli3 脚手架&#xff0c;引用 Element UI 组件库&#xff0c;数据库直连mysql方便开发快速简洁好看的组件。 功能包含如下&#…

EtherNet/IP转Modbus TCP协议网关的接口

远创智控的YC-EIPM-TCP网关产品&#xff0c;它有什么作用呢&#xff1f;一起来了解一下吧&#xff01; 远创智控YC-EIPM-TCP网关产品可以通过各种数据接口和工业领域的仪表、PLC、计量设备等产品连接&#xff0c;实时采集这些设备中的运行数据、状态数据等信息&#xff0c;并把…

【javascript】内部引入与外部引入javascript

创建a.html 内部引入&#xff1a; 外部引入&#xff1a; 创建a.js 注意&#xff1a; 我这里的a.js和a.html是放在同一个目录下&#xff0c;如果a.js放在js的目录下&#xff0c;a.html 调用a.js的时候 <script src"/js/a.js"></script>

sqlmap --os-shell选项原理解析

文章目录 sqlmap --os-shell选项原理解析原理解析总结 sqlmap --os-shell选项原理解析 以sqli第一关为例。 --os-shell 是 SQLMap 工具的一个参数&#xff0c;用于在成功注入数据库后&#xff0c;执行操作系统命令并获取其输出。 sqlmap -u "http://192.168.188.199/sq…