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

这篇具有很好参考价值的文章主要介绍了学C的第三十四天【程序环境和预处理】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

相关代码gitee自取

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

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

接上期

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

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

                     

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

                 

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

               

(1). 翻译环境:

            

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

                  

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

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

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

                     


                    

(2). 执行环境:

                  

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

         

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

             

2 . 翻译环境

                        

翻译环境包括 编译链接

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

                

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

                     

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

       

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

            

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

                   

图解:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     


                    

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

                    

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

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

                

                

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

                 

生成文件后缀xxx.i

该阶段进行一些文本操作

                    

       包括

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

             

图解:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                  

                  

(2.2). 编译阶段

                 

生成文件后缀xxx.s

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

                      

       包括

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

              

图解:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                

                  

(2.3). 汇编阶段

                 

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

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

            

编译阶段进行符号汇总

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

以便链接时使用

              

图解:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                   


                    

(3). 链接:

             

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

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

                    

       包括

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

              

图解:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

         

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

                     

3 . 预处理详解

(1). 预定义符号

                  

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

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

               

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     


                    

(2). #define

                   

(2.1). #define 定义标识符

                

写法:

#define name stuff

                   

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

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                      

                      

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

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

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                       

               

(2.2). #define 定义宏

                

写法:

#define name( parament-list ) stuff

                   

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

               

注意:

定义宏时

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

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

         

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                    

注意:

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

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

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

操作符优先级问题)。

                       

               

(2.3). #define 替换规则

                

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

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

                 

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

                         

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     

               

(2.4). # 和 ##

               

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

                      

#:

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

                  

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                

##:

               

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

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

             

注意:

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

         

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                       

               

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

                     

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

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

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

           

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                       

               

(2.6). 宏和函数对比

                

属 性 #define定义宏 函数

代 码

长 度

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

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

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

执 行

速 度

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

参 数

类 型

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

                       

               

(2.7). 命名约定

                     

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

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

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

                     


                    

(3). #undef

                

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

                

写法:

#undef NAMENAME

                   

  • NAME  --  要移除的宏的名字
示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     


                    

(4). 命令行定义

                

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

VS不行gcc可以

             

例如:

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

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

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

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

              

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     


                    

(5). 条件编译

                 

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

实现选择性的编译

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

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

           

           

常见的条件编译指令:

                 

指令一:单个分支的条件编译
                                #if  常量表达式                         
                                    //一条(一组)语句...                               
                                #endif                                       
                                //常量表达式由预处理器求值。
示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                

                

指令二:多个分支的条件编译
                                #if 常量表达式
                                     //...
                                #elif 常量表达式
                                     //...
                                #else
                                     //...
                                #endif
示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                

                

指令三:判断是否被定义的条件编译
                                //如果定义过:
                                #if defined(symbol)  //第一种写法
                                #ifdef symbol        //第二种写法
                                
                                //如果未定义过:
                                #if !defined(symbol) //第一种写法
                                #ifndef symbol       //第二种写法

                                //symbol -- 定义的符号
示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                

                

指令四:嵌套指令

               

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

          

示例:

学C的第三十四天【程序环境和预处理】,CCC全是C,c语言

                     


                    

(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函数;也有可能是意外终止文章来源地址https://www.toymoban.com/news/detail-663832.html

到了这里,关于学C的第三十四天【程序环境和预处理】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第四十四天打卡

    零钱兑换 II 中等 1K 相关企业 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号

    2024年02月03日
    浏览(49)
  • 【力扣刷题 | 第二十四天】

    目录 前言: 1049. 最后一块石头的重量 II - 力扣(LeetCode) 494. 目标和 - 力扣(LeetCode) 总结:                  今天我们依然暴打动态规划 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出任意两块石头,然后将它们一起粉

    2024年02月14日
    浏览(38)
  • 算法练习第六十四天

    LCR 184. 设计自助结算系统 - 力扣(LeetCode) 总结:利用一个双端维护队列一个往后递减的队列,对头是最大值,每次进入一个新值时就一直和队尾元素比较将比新的值小的数排出,这样能保证留在队列中的数都是会对最大值产生影响的数,而当主队列中将要排出的数与双端队

    2024年02月07日
    浏览(58)
  • MFC补充第十四天 句柄嫁接与子类化

    句柄嫁接与子类化: a)Attach和Detach就是单纯的嫁接与分离函数。 对象一旦嫁接入一个句柄,就可以自由地调用CWnd或其派生类的功能。 b)子类化Subclass内部包含Attach,额外再增加一个消息转拨到派生类(SubClass就是子类) c)SubClassWindow函数内部核心功能就是Attach和::SetWindowLong

    2024年02月16日
    浏览(48)
  • 十四天学会C++之第五天:类的详细讨论

    什么是友元函数和友元类,它们的作用。 如何声明和使用友元函数和友元类,访问类的私有成员。 友元函数(Friend Functions) 友元函数是一种特殊的函数,它被允许访问类的私有成员。这意味着即使成员是私有的,友元函数也能够直接访问它们,而不需要通过公有接口。这提

    2024年02月07日
    浏览(45)
  • 十四天学会C++之第一天(入门和基本语法)

    C++诞生于20世纪80年代初,它的创造者是计算机科学家Bjarne Stroustrup。当时,Stroustrup在贝尔实验室工作,他希望为C语言添加一些功能,以便更好地支持系统开发。这个愿望促使他创建了C++。 C++的名字来源于它的基因,其中的\\\"C\\\"代表了C语言,而\\\"++\\\"表示C语言的一个增强版本。这

    2024年02月07日
    浏览(51)
  • 蓝桥杯十四天冲刺班 第十四天《考场经验 | 历年考点 | 蓝桥杯押题》《C,JAVA,PY在蓝桥杯中必须要会用的容器 | 集合》(3K+字解析)

     📒博客首页:Sonesang的博客 🎉欢迎关注🔎点赞👍收藏⭐️留言📝 ❤️ :热爱Java与算法学习,期待一起交流! 🙏作者水平很有限,如果发现错误,求告知,多谢! 🌺有问题可私信交流!!!   目录 算法 实力 = 知识点+刷题量+速度+灵活的大脑 C++组知识点 java组知识点

    2023年04月15日
    浏览(42)
  • Python学习笔记第六十四天(Matplotlib 网格线)

    我们可以使用 pyplot 中的 grid() 方法来设置图表中的网格线。 grid() 方法语法格式如下: 参数说明: b:可选,默认为 None,可以设置布尔值,true 为显示网格线,false 为不显示,如果设置 **kwargs 参数,则值为 true。 which:可选,可选值有 ‘major’、‘minor’ 和 ‘both’,默认为

    2024年02月12日
    浏览(44)
  • 第五十四天学习记录:C语言进阶:动态内存管理Ⅱ

    1、对NULL指针的解引用操作 2、对动态开辟的内存的越界访问 3、对非动态开辟内存的free 4、使用free释放动态开辟内存的一部分 5、对同一块动态内存多次释放 6、动态开辟内存忘记释放(内存泄漏) 问:realloc的第一个参数的指针地址必须是malloc或calloc创建的在堆上的地址吗?

    2024年02月06日
    浏览(39)
  • 从零开始的力扣刷题记录-第六十四天

    题目描述: 给你一个下标从 0 开始的整数数组 nums 。在一步操作中,你可以执行以下步骤: 从 nums 选出 两个 相等的 整数 从 nums 中移除这两个整数,形成一个 数对 请你在 nums 上多次执行此操作直到无法继续执行。 返回一个下标从 0 开始、长度为 2 的整数数组 answer 作为答

    2024年02月11日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包