学C的第三十四天【程序环境和预处理】

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客

 =========================================================================

                     

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

                 

ANSI C(C语言标准)任何一种实现中存在两个不同的环境

               

(1). 翻译环境:

            

在这个环境中源代码转换为可执行的机器指令

                  

计算机能够执行二进制指令

但我们平常写的C语言代码文本信息计算机不能直接执行

翻译环境就可以把C语言代码(源代码)翻译为二进制指令(可执行的机器指令

                     


                    

(2). 执行环境:

                  

在这个环境下可以执行二进制的代码(实际执行代码)

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2 . 翻译环境

                        

翻译环境包括 编译链接

编译又包括 预编译(预处理)编译汇编

                

(1). 翻译环境下的 编译 和 链接:

                     

  • 组成一个程序的每个源文件通过编译过程(各自进行编译)分别转换成目标代码(object code)

       

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

            

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

                   

图解:

                     


                    

(2). 编译的几个阶段:

                    

VS这种集成开发环境不方便观察编译各阶段细节

Linux系统下使用gcc编译器更好观察

                

                

(2.1). 预编译(预处理) 阶段

                 

生成文件后缀xxx.i

该阶段进行一些文本操作

                    

       包括

  • 注释删除
  • #include 头文件包含
  • #define 符号替换
  • 所有预处理指令都是在预处理阶段处理

             

图解:

                  

                  

(2.2). 编译阶段

                 

生成文件后缀xxx.s

该阶段把C语言代码翻译为汇编指令

                      

       包括

  • 语法分析
  • 词法分析
  • 语义分析
  • 符号分析

              

图解:

                

                  

(2.3). 汇编阶段

                 

生成文件后缀xxx.o  (object目标文件)

该阶段将编译阶段完成的汇编指令翻译为二进制指令

            

编译阶段进行符号汇总

该阶段则会形成对应的符号表

以便链接时使用

              

图解:

                   


                    

(3). 链接:

             

编译生成的目标文件进行操作

生成可执行程序(也是二进制文件)

                    

       包括

  • 合并段表
  • 符号表(由编译的汇编阶段形成)的合并符号表重定位

              

图解:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

3 . 预处理详解

(1). 预定义符号

                  

C语言预定义了一些符号,这些预定义符号都是语言内置的

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

               

示例:

                     


                    

(2). #define

                   

(2.1). #define 定义标识符

                

写法:

#define name stuff

                   

  • name -- 定义的标识符名称
  • stuff -- 定义的标识符内容

示例:

                      

                      

define定义标识符的时候,最好不要在最后加上“分号 ;”

因为有些编译器可能会把“分号;”也当作stuff(标识符的内容)

示例:

                       

               

(2.2). #define 定义宏

                

写法:

#define name( parament-list ) stuff

                   

  • name -- 定义的宏的名称
  • parament-list -- 参数列表参数会替换放到 stuff 中
  • stuff -- 定义的宏的内容

               

注意:

定义宏时

参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在

参数列表就会被解释为stuff的一部分

         

示例:

                    

注意:

用于对数值表达式进行求值的宏定义都应该用上图这种方式加上括号

对stuff中的各个参数分别加上括号对stuff整体加上括号

避免在使用宏时由于参数中的操作符邻近操作符之间不可预料的相互作用

操作符优先级问题)。

                       

               

(2.3). #define 替换规则

                

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

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

                 

注意:

  • 宏参数#define 定义中可以出现其他#define定义的符号
    但是对于宏不能出现递归。(和函数的区别
  • 预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

                         

示例:

                     

               

(2.4). # 和 ##

               

### 这两个符号只能在宏里面使用

                      

#:

                  
该符号可以把宏的参数以字符串的形式插入到字符串中

                  

示例:

                

##:

               

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

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

             

注意:

这样的连接必须产生一个合法的标识符否则其结果就是未定义的。

         

示例:

                       

               

(2.5). 带副作用的宏参数

                     

宏参数宏的定义中出现超过一次的时候,如果参数带有副作用

那么你在使用这个宏的时候就可能出现危险导致不可预测的后果

副作用就是表达式求值的时候出现的永久性效果

           

示例:

                       

               

(2.6). 宏和函数对比

                

属 性#define定义宏函数

代 码

长 度

每次使用时,宏代码都会被插入到程序中

除了非常小的宏之外程序的长度会大幅度增长

函数代码只出现于一个地方每次使用这个函数时,都调用那个地方的同一份代码

执 行

速 度

更快存在函数的调用和返回额外开 销,所以相对慢一些
操作符 优先级宏参数的求值在所有周围表达式的上下文环境里, 除非加上括号否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号函数参数只在函数调用的时候求值一次,它的结果值传递给函数表达式的求值结果更容易预测
带有副 作用的 参数参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果函数参数只在传参的时候求值一 次结果更容易控制

参 数

类 型

宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型函数的参数与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调 试宏是不方便调试函数是可以逐语句调试
递 归宏是不能递归函数是可以递归

                       

               

(2.7). 命名约定

                     

一般来讲,函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。

所以我们从书写上进行区分

  • 宏名全部大写
  • 函数名不要全部大写

                     


                    

(3). #undef

                

这个指令用于移除一个宏定义移除之后不能再使用

                

写法:

#undef NAMENAME

                   

  • NAME  --  要移除的宏的名字

示例:

                     


                    

(4). 命令行定义

                

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

VS不行gcc可以

             

例如:

当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性会有点用处。

(假定某个程序中声明了一个某个长度的数组,如果机器内存有限

我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些

这时候就可以用命令行定义灵活调整

              

示例:

                     


                    

(5). 条件编译

                 

使用条件编译指令可以决定一条(一组)语句是否进行编译

实现选择性的编译

条件编译指令如果为真,则编译时内容保留

条件编译指令如果为假,则编译时内容删除

           

           

常见的条件编译指令:

                 

指令一:单个分支的条件编译

                                #if  常量表达式                         //一条(一组)语句...                               #endif                                       //常量表达式由预处理器求值。
示例:

                

                

指令二:多个分支的条件编译

                                #if 常量表达式//...#elif 常量表达式//...#else//...#endif
示例:

                

                

指令三:判断是否被定义的条件编译

                                //如果定义过:#if defined(symbol)  //第一种写法#ifdef symbol        //第二种写法//如果未定义过:#if !defined(symbol) //第一种写法#ifndef symbol       //第二种写法//symbol -- 定义的符号
示例:

                

                

指令四:嵌套指令

               

将上面的三种条件编译组合使用

          

示例:

                     


                    

(6). 文件包含

                        

#include指令 可以使另外一个文件被编译

就像它实际出现于 #include指令 的地方一样。

这种替换的方式很简单:

预处理器先删除这条指令,并用包含文件的内容替换

这样一个源文件被包含10次,那就实际被编译10次

                 

                   

头文件被包含的方式:

            

本地文件包含

写法:

#include "filename"

               

查找策略:

先在源文件所在目录下查找,如果该头文件未找到

编译器就像查找库函数头文件一样在标准位置查找头文件

如果找不到就提示编译错误

                 

                   

库文件包含

写法:

#include <filename.h>

               

查找策略:

查找头文件直接去标准路径下去查找

如果找不到就提示编译错误

                 

                   

嵌套文件包含:

                   

如果一个文件有很多头文件

另一个文件包含了该文件,同时该文件也有头文件

再有一个文件包含这两个头文件

那么同一个头文件就有可能重复出现在一个文件中

           

可以使用条件编译来防止头文件重复出现

                                //每个头文件的开头写:#ifndef __TEST_H__#define __TEST_H__//头文件的内容#endif   //__TEST_H__//或者:#pragma once//头文件的内容

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

4 . 运行环境

程序执行的过程:

            

1. 程序必须载入内存中

有操作系统的环境中程序载入内存一般这个由操作系统完成

独立的环境中,程序的载入必须由手工安排

可能是通过可执行代码置入只读内存来完成。

                    

            

2.

程序执行开始,接着便调用main函数

                

            

3. 开始执行程序代码

这个时候程序将使用一个运行时堆栈(stack)(函数栈帧)

存储函数的局部变量返回地址

程序同时也可以使用静态(static)内存

存储于静态内存中的变量在程序的整个执行过程一直保留他们的值

              

            

4. 终止程序

正常终止main函数;也有可能是意外终止

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

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

相关文章

如何取消订阅IEEE membership的email

最近小虎开了一个IEEE Student Member&#xff0c;邮箱都快被IEEE给爆箱了。所以想办法取消订阅其邮件&#xff0c;但是保留其member身份。 方法 在profile界面选择communication preferences and policies, Uncheck所有communications&#xff0c;选择I only want to recei…

Java之接口

作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; Java之接口 接口的概念语法规则接口特性接口使用案…

DTC 19服务学习2

紧跟上篇 0x04 reportDTCSnapshotRecordByDTCNumber 通过DTC和快照序列来获取DTC快照记录。 适用以下假设&#xff1a; — 服务器支持存储给定 DTC 的两个 DTCSnapshot 记录的能力。 — 此示例假定是上一个示例的延续。 — 假设服务器请求服务器存储的 DTC 编号 123456 的两个…

攻防世界-backup

原题 解题思路 备份文件后缀大多是bak、git、svn、swp等&#xff0c;尝试index.php.bak就有文件下载了:

1、攻防世界第一天

1、网站目录下会有一个robots.txt文件&#xff0c;规定爬虫可以/不可以爬取的网站。 2、URL编码细则&#xff1a;URL栏中字符若出现非ASCII字符&#xff0c;则对其进行URL编码&#xff0c;浏览器将该请求发给服务端&#xff1b;服务端会可能会先对收到的url进行解码&#xff0…

【HBZ分享】java中的BitSet 与 Redis中的BitMap 与 布隆过滤器

BitMap的存储原理 bitMap他会标识出某个整数是否存在&#xff0c;存在即为1&#xff0c;不存在对应位即为0bitMap是存储int类型的&#xff0c;int 4byte&#xff0c; 1byte 8bit&#xff0c;因此bitMap数组中的每个下标可以标识出32个数字是否存在bitMap相当于一个个小格子&…

解决方案:如何在 Amazon EMR Serverless 上执行纯 SQL 文件?

《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书由博主历时三年精心创作&#xff0c;现已通过知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详…

IP 地址监控工具

地址监控实用程序是一套 IP 工具&#xff0c;包括 IP 地址监控工具、流氓检测工具和 MAC 地址解析器&#xff0c;用于日常监控和管理 DNS 名称、IP和 MAC 地址。地址监控工具用于 IP监控&#xff0c;用于管理 DNS 名称、网络的 IP 和 MAC 地址&#xff0c;并跟踪 IP 地址。 IP…

uniapp配置添加阿里巴巴图标icon流程步骤

文章目录 下载复制文件到项目文件夹里项目配置目录结构显示图标 下载 阿里巴巴icon官网 https://www.iconfont.cn/ 复制文件到项目文件夹里 项目配置目录结构 显示图标

智能监控系统的守护者:人工智能行为识别技术的崛起与发展

人工智能助力监控系统&#xff1a;行为识别在安全监控中的应用与挑战 摘要&#xff1a; 随着人工智能技术的快速发展&#xff0c;行为识别在监控系统中的应用逐渐成为安全监控领域的重要工具。本文将详细探讨人工智能行为识别技术在监控系统中的应用&#xff0c;以及在实际应用…

JVM中分代回收机制

为什么要分为新生代和老年代&#xff1f; 分为新生代&#xff08;Young Generation&#xff09;和老年代&#xff08;Old Generation&#xff09;是为了更有效地管理和优化内存的使用。 新生代主要存放生命周期较短的对象&#xff0c;例如方法的局部变量、临时变量等。由于这…

数据结构-二叉树

在学习二叉树之前.必须先要掌握一些树的重要概念: 结点的度:一个结点含有的子树个数称为该结点的度.树的度:一棵树中,所有节点度的最大值称为树的度.叶子结点:度为0的结点称为叶子节点.(也叫终端结点)双亲结点:若一个结点含有子结点,则这个结点称为其子结点的双亲结点(也叫父节…

Egg.js构建一个stream流式接口服务

经常需要用到 stream 流式接口服务,比如&#xff1a;大文件下载、日志实时输出等等。本文将介绍如何使用Egg.js构建一个 stream 流式接口服务。 一、准备工作 目录结构&#xff1a; app//controllerindex.jstest.txttest.shindex.js 控制器test.txt 测试文件&#xff0c;最好…

申请部署阿里云SSL免费证书

使用宝塔自动创建的证书有时候会报NET::ERR_CERT_COMMON_NAME_INVALID&#xff0c;并且每次只能三个月&#xff0c;需要点击续期非常麻烦&#xff0c;容易遗忘。 阿里云免费SSL证书 前往阿里云管理控制台【数字证书管理服务】【SSL证书】&#xff0c;每年20个额度&#xff0c;一…

17.HPA和rancher

文章目录 HPA部署 metrics-server部署HPA Rancher部署Rancherrancher添加集群仪表盘创建 namespace仪表盘创建 Deployments仪表盘创建 service 总结 HPA HPA&#xff08;Horizontal Pod Autoscaling&#xff09;Pod 水平自动伸缩&#xff0c;Kubernetes 有一个 HPA 的资源&…

docker+haror

docker 2013年诞生&#xff0c;推荐单容器只运行一个程序或进程&#xff0c;形成一个分布式的应用模型。 总结下来就是&#xff1a;docker带来启动流程更快&#xff0c;运行效率较高、资源损耗较小&#xff0c;属于轻量级的服务。 docker的安装 推荐的一键化安装的脚本&#…

STM8遇坑[EEPROM读取debug不正常release正常][ STVP下载成功单运行不成功][定时器消抖莫名其妙的跑不通流程]

EEPROM读取debug不正常release正常 这个超级无语,研究和半天,突然发现调到release就正常了,表现为写入看起来正常读取不正常,这个无语了,不想研究了 STVP下载不能够成功运行 本文摘录于&#xff1a;https://blog.csdn.net/qlexcel/article/details/71270780只是做学习备份之…

Python面向对象植物大战僵尸

先来一波效果图 来看看如何设计游戏架构 import sysimport pygameclass BaseSprite(pygame.sprite.Sprite):def __init__(self, name):super().__init__()self.image pygame.image.load(name)self.rect self.image.get_rect()class AnimateSprite(BaseSprite):def __init__(…

Vue 2 处理边界情况

访问元素和组件 通过Vue 2 组件基础一文的学习&#xff0c;我们知道组件之间可以通过传递props或事件来进行通信。 但在一些情况下&#xff0c;我们使用下面的方法将更有用。 1.访问根实例 根实例可通过this.$root获取。 我们在所有子组件中都可以像上面那样访问根实例&…

什么文件传输协议才能保障跨国文件传输安全又稳定

在当今的全球化时代&#xff0c;跨国文件传输是一种常见而又重要的需求&#xff0c;无论是个人还是企业&#xff0c;都需要通过网络来分享和交换各种类型和大小的文件。但是&#xff0c;跨国文件传输也面临着许多挑战和风险&#xff0c;如何选择一个合适的文件传输协议&#xf…