程序环境和预处理:

  • 翻译环境和执行环境
  • 条件编译

翻译环境和执行环境

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

翻译环境

我们写的代码是如何从一开始的.c文件变化为可执行的.exe文件,就是在这一步完成的。

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码
  • 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且还可以搜索本地的程序库,找到需要的函数链接进程序中

下面就来详细讲一下编译的具体过程

编译分为三个阶段,预处理,编译,汇编。

1.预处理

步骤:

  • 展开头文件
  • 宏替换
  • 去掉注释
  • 条件编译

经过上述步骤后将.c文件处理成为.i文件

2.编译

步骤:

  • 语法检查
  • 将代码转换为汇编代码

这时.i文件会被处理成.s文件

3.汇编

步骤:

  • 将汇编代码转换成为机器所能识别的二进制

这一步后,s文件被处理成为.o文件。

编译器的工作完成以后,就到了链接器的工作

链接

步骤:

  • 将声明却又未定义的函数在其他文件中找到

这时候,多个.o文件将被处理成为可执行的exe文件。


运行环境

程序执行的过程:

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

#define 定义宏

#define允许我们将一个文本替换另一个文本,也允许我们将参数替换到文本当中,这种实现通常称之为宏

#define 替换 被替换

例如:

#define type int
#define num 100
int main()
{int a = 100;type  b = num;printf("%d %d\n", a, b);
}


它们运行后是一样的,宏的作用就是将一个参数或者标识符在预处理阶段就可以替换成我们想要的名字。

宏也可以当作函数使用
声明方法:

#define name( parament-list ) stuff

注意:参数列表的左括号必须与name紧邻。如果两者之间存在空格,参数列表就会被解释为stuff的一部分

如果是对宏不太了解的人,可能会遇到下面这种情况

#define SQUARE( x ) x * x
int main()
{int a = 5;printf("%d\n", SQUARE(5));printf("%d\n", SQUARE(5 + 1));
}


第一个答案是我们意料中的,但是第二个答案却让人感到奇怪,明明我们的参数是5 + 1,得到的应该是6的平方36,为什么会是这个奇怪的值呢?

之前我提到过,宏是单纯的替换,而非计算后再替换,所以替换后的公式如下


所以如果我们要使用宏函数,就必须要考虑到优先级问题,所以要在定义宏时加上括号

#define SQUARE( x ) ( x ) * ( x )

#define ADD(x) (x) + (x)
int main()
{int a = 5;printf("%d\n", 10 * ADD(a));
}


对于这个新的宏,我们按照之前的做法加上了括号,但是得到的答案还是错的,我们再次将宏替换进代码中

这个问题还是优先级的问题,所以我们需要再在外面加上一层括号

#define ADD(x) ((x) + (x))


总结:所以用于对数值表达式进行求值的宏定义都应该用这种方法加上括号,避免在使用宏时由于参数中的操作符或临近操作符之间不可预料的相互作用

#define的替换规则

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

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

注意:

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

宏与函数的对比

优点:

  1. 程序规模和速度更胜一筹
  2. 宏的参数与类型无关

缺点:

  1. 宏替换时会重复将代码插入程序,造成代码冗余。
  2. 宏无法进行调试
  3. 宏与类型无关,所以不够严谨
  4. 宏如果使用不当容易出现运算符优先级问题

条件编译

条件编译,顾名思义,就是满足条件才编译某些语句,不满足则不编译。

在我们编写代码的时候,可能存在一种情况,这段语句在某个环境下是需要的,在另一个环境下却又不需要,这个时候我们如果要在它不用的时候将它删除,就会十分麻烦,而使用条件编译就可以解决这个方法。

例如当我们一个项目中多次引用一个头文件,造成在最终程序中出现了多份这个头文件的内容,导致了文件内容重复的情况,这个时候我们就需要这个条件编译

#ifndef __TEST_H__
#define __TEST_H__
#endif

这就是一种条件编译

常见的条件编译如下:
1.

#if (常量表达式)#endif

多个分支的条件编译

#if (常量表达式)
#elif (常量表达式)
#else
#endif

判断是否被定义

#if defined(x)
#ifdef x#if !defined(x)
#ifndef x

嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif

C语言程序设计 | 程序环境和预处理:翻译环境和执行环境、宏、条件编译相关推荐

  1. C语言程序设计 | 程序编译与预处理

    目录 一.程序的翻译环境和执行环境 二.详解编译+链接 1.翻译环境 2.编译 2.1预编译/预处理 2.2编译 2.3汇编 三.预处理详解 1.预定义符号 2.#define 2.1#define定 ...

  2. bilibiliclass76-80_C语言_程序的编译(预处理操作)+链接

    程序环境和预处理 本章重点: 程序的翻译环境 程序的执行环境 详解:C语言程序的编译+链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 ...

  3. JavaScript执行环境及作用域(一)——执行环境栈和作用域链机制

    2019独角兽企业重金招聘Python工程师标准>>> 执行环境是JavaScript中最为重要的一个概念,每个执行环境都有一个与之关联的变量对象,执行环境中所有的变量和函数都保存在 ...

  4. 《javascript高级程序设计》笔记:内存与执行环境

    上一篇:<javascript高级程序设计>笔记:继承 近几篇博客都会围绕着图中的知识点展开 由于javascript是一门具有自动垃圾收集机制的编程语言,开发者不必担心内存的分配和回收的 ...

  5. 嵩天老师-Python语言程序设计-Python123配套练习测验题目汇总整理

    测验1:Python基本语法元素 知识点概要: 普遍认为Python语言诞生于1991年 Python语言中的缩进在程序中长度统一且强制使用,只要统一即可,不一定是4个空格(尽管这是惯例) IPO模型 ...

  6. JavaScript语言精粹--执行环境及作用域,this

    1.执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为. 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 虽然我们无法访问,但是解析器在处理数据时 ...

  7. JavaScript执行环境 + 变量对象 + 作用域链 + 闭包

    闭包真的是一个谈烂掉的内容.说到闭包,自然就涉及到执行环境.变量对象以及作用域链.汤姆大叔翻译的<深入理解JavaScript系列>很好,帮我解决了一直以来似懂非懂的很多问题,包括闭包.下 ...

  8. html代码在线运行环境,ES5/可执行代码与执行环境

    可执行代码类型 一共有三种 ECMA 脚本可执行代码: 全局代码是指被作为 ECMAScript Eval 代码是指提供给 eval 内置函数的源代码文本.更精确地说,如果传递给 eval 内置函数的 ...

  9. SparkContext的初始化(伯篇)——执行环境与元数据清理器

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

最新文章

  1. python字典去最值_python 比较字典value的最大值的几种方法
  2. Struts2环境下Tomcat启动异常:Exception starting filter struts2,报了一个java.lang.ClassNotFoundException
  3. mmTrix大数据分析平台构建实录--转
  4. fmt.Println、fmt.Printf、fmt.Sprintf、fmt.Sprintln
  5. javaSE各阶段练习题--数组
  6. 网络编程-TCP/IP协议栈-TCP协议
  7. (3)Linux进程调度-进程切换
  8. 李宏毅-《深度学习人类语言处理》国语版(2020)视频课程及ppt分享
  9. baacloud无法连接到_加入 Beta 版“Baacloud” - TestFlight - Apple
  10. Excel怎么快速对比两个工作表的异同
  11. Mysql8中降序索引的底层实现
  12. 安装gentoo折腾
  13. 使用 mv 命令移动文件夹
  14. TunesKit Video Cutter for Mac(视频分割编辑工具)
  15. 利用Kalibr标定Camera-IMU外参
  16. 班章管家理财入门基础常识有什么?理财产品怎样买最合适
  17. js处理json数组
  18. 老年性痴呆,需要心理关爱
  19. 【ES实战】Elasticsearch指标监控说明
  20. 5G加速,为什么说紫光股份是运营商市场的大赢家?

热门文章

  1. 通过Zuul上传文件,禁用Zuul的Filters
  2. SpringCloud:Ribbon负载均衡(基本使用、 负载均衡、自定义配置、禁用 Eureka 实现 Ribbon 调用)
  3. ajax创建对象,ajax创建对象
  4. javascript等待异步线程完成_作为前端你了解JavaScript运行机制吗?
  5. oracle 11.2 安装asm,oracle11r2安装asm+rac配置步骤.doc
  6. kotlin使用spring data redis(二)
  7. Ubuntu 16.04 安装wine
  8. WRC 2017最值得关注的专题论坛之一,与大咖探讨青年科学家的创新创业
  9. linux中redis的主从
  10. Java程序员从笨鸟到菜鸟之(九十九)深入java虚拟机(八)开发自己的类加载器...