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

news/2024/5/20 22:21:00/文章来源:https://blog.csdn.net/weixin_63176266/article/details/132267466

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

相关代码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.luyixian.cn/news_show_536494.aspx

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

相关文章

PyTorch学习笔记(十六)——利用GPU训练

一、方式一 网络模型、损失函数、数据&#xff08;包括输入、标注&#xff09; 找到以上三种变量&#xff0c;调用它们的.cuda()&#xff0c;再返回即可 if torch.cuda.is_available():mynn mynn.cuda() if torch.cuda.is_available():loss_function loss_function.cuda(…

1339. 分裂二叉树的最大乘积

链接&#xff1a; ​​​​​​1339. 分裂二叉树的最大乘积 题解&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* …

Java性能分析中常用命令和工具

当涉及到 Java 性能分析时&#xff0c;有一系列强大的命令和工具可以帮助开发人员分析应用程序的性能瓶颈、内存使用情况和线程问题。以下是一些常用的 Java 性能分析命令和工具&#xff0c;以及它们的详细说明和示例。 以下是一些常用的性能分析命令和工具汇总&#xff1a; …

Nacos配置管理、Feign远程调用、Gateway服务网关

1.Nacos配置管理 1.1.将配置交给Nacos管理的步骤 1.在Nacos中添加配置 Data Id服务名称-环境名称.yaml eg&#xff1a;userservice-dev.yaml 2.引入nacos-config依赖 在user-service服务中&#xff0c;引入nacos-config的客户端依赖 <!--nacos配置管理依赖--> <dep…

redis--主从复制

redis主从复制 Redis 主从复制是一种用于实现数据复制和数据备份的机制&#xff0c;它允许将一个 Redis 服务器的数据复制到其他 Redis 服务器上。主从复制在 Redis 中通常用于构建高可用性架构、读写分离以及数据分析等场景。 主从复制的角色 主服务器&#xff08;Master&a…

系统架构设计专业技能 · 软件工程之需求工程

系列文章目录 系统架构设计高级技能 软件架构概念、架构风格、ABSD、架构复用、DSSA&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 系统质量属性与架构评估&#xff08;二&#xff09;【系统架构设计师】 系统架构设计高级技能 软件可靠性分析与设计…

Cpp学习——类与对象3

目录 一&#xff0c;初始化列表 1.初始化列表的使用 2.初始化列表的特点 3.必须要使用初始化列表的场景 二&#xff0c;单参数构造函数的隐式类型转换 1.内置类型的隐式类型转换 2. 自定义类型的隐式类型转换 3.多参数构造函数的隐式类型转换 4.当你不想要发生隐式类型转换…

Unity VR:XR Interaction Toolkit 输入系统(Input System):获取手柄的输入

文章目录 &#x1f4d5;教程说明&#x1f4d5;Input System 和 XR Input Subsystem&#xff08;推荐 Input System&#xff09;&#x1f4d5;Input Action Asset⭐Actions Maps⭐Actions⭐Action Properties&#x1f50d;Action Type (Value, Button, Pass through) ⭐Binding …

数据结构<树和二叉树>顺序表存储二叉树实现堆排

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

Gin+微服务实现抖音视频上传到七牛云

文章目录 安装获取凭证Gin处理微服务处理 如果你对Gin和微服务有一定了解&#xff0c;看本文较容易。 安装 执行命令&#xff1a; go get github.com/qiniu/go-sdk/v7获取凭证 Go SDK 的所有的功能&#xff0c;都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的A…

Go语言入门指南:基础语法和常用特性解析(上)

一、Go语言前言 Go是一种静态类型的编译语言&#xff0c;常常被称作是21世纪的C语言。Go语言是一个开源项目&#xff0c;可以免费获取编译器、库、配套工具的源代码&#xff0c;也是高性能服务器和应用程序的热门选择。 Go语言可以运行在类UNIX系统——比如Linux、OpenBSD、M…

Python批量爬虫下载文件——把Excel中的超链接快速变成网址

本文的背景是&#xff1a;大学关系很好的老师问我能不能把Excel中1000个超链接网址对应的pdf文档下载下来。虽然可以手动一个一个点击下载&#xff0c;但是这样太费人力和时间了。我想起了之前的爬虫经验&#xff0c;给老师分析了一下可行性&#xff0c;就动手实践了。    没…

Platypus:Quick,Cheap,and Powerful Refinement of LLMs

Platypus:Quick,Cheap,and Powerful Refinement of LLMs IntroductionMethod2.1 Curating Open- PlatypusRemoving similar&duplicate questionsContamination CheckFine-tuning & mergingResult参考Introduction 现在大模型已经取得很不错的结果,如何把大模型的能…

sh 脚本循环语句和正则表达式

目录 1、循环语句 1、for 2、while 3、until 2、正则表达式 1、元字符 2、表示次数 3、位置锚定 4、分组 5、扩展正则表达式 1、循环语句 循环含义 将某代码段重复运行多次&#xff0c;通常有进入循环的条件和退出循环的条件 重复运行次数 循环次数事先已知 循环次…

1、攻防世界第一天

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

使用 Amazon Redshift Serverless 和 Toucan 构建数据故事应用程序

这是由 Toucan 的解决方案工程师 Django Bouchez与亚马逊云科技共同撰写的特约文章。 带有控制面板、报告和分析的商业智能&#xff08;BI&#xff0c;Business Intelligence&#xff09;仍是最受欢迎的数据和分析使用场景之一。它为业务分析师和经理提供企业的过去状态和当前状…

字符设备驱动实例(PWM和RTC)

目录 五、PWM 六、RTC 五、PWM PWM(Pulse Width Modulation&#xff0c;脉宽调制器)&#xff0c;顾名思义就是一个输出脉冲宽度可以调整的硬件器件&#xff0c;其实它不仅脉冲宽度可调&#xff0c;频率也可以调整。它的核心部件是一个硬件定时器&#xff0c;其工作原理可以用…

抖音火山引擎推出免费域名DNS和公共DNS服务

抖音旗下的云计算服务火山引擎最近推出了"TrafficRoute DNS 套件"服务&#xff0c;其中包括两款产品&#xff0c;对软希网来说非常有用。 1.域名DNS&#xff1a; 这是一个用于网站域名的DNS服务&#xff0c;可以加速域名解析速度&#xff0c;从而提升网站的速度。如…

初出茅庐的小李博客之STM32CubeMx驱动WS2812B实现幻彩(超详)

STM32CubeMx驱动WS2812B实现幻彩&#xff08;超详&#xff09; 1.创建基于STM32F03C8T6工程 1.1配置时钟 选择外部高速时钟源HSE 1.2配置系统时钟树使其达到最大时钟72MHz&#xff08;最大系统时钟&#xff09; 由时钟树可以知道APB1上定时器时钟频率是72MHz,实验使用的硬件…

[NLP] BERT模型参数量

一 BERT_Base 110M参数拆解 BERT_base模型的110M的参数具体是如何组成的呢&#xff0c;我们一起来计算一下&#xff1a; 刚好也能更深入地了解一下Transformer Encoder模型的架构细节。 借助transformers模块查看一下模型的架构&#xff1a; import torch from transformers …