C语言——程序环境

一、程序环境

在ANSI C的任何一种实现中,存在两个不同的环境:

  • 翻译环境:这个环境中源代码被转换为可执行的机器指令。
  • ;运行环境:它用于实际运行代码。

1、翻译环境具体步骤

翻译环境(Translation Environment)是指将C语言源代码转换成可执行的机器代码的过程。这个过程通常包括以下几个步骤:

1)预处理 / 预编译(Preprocessing):

  • 处理源代码中的预处理指令,如#include#define#ifdef等。
  • 宏替换,将宏定义替换为实际的代码。
  • 条件编译,根据条件包含或排除某些代码块。
  • 删除注释。
  • 文件包含,将头文件的内容插入到源代码中。

最终生成一个没有预处理指令的纯C代码文件。

如果我们使用gcc来编译代码,这一步生成的结果我们可以通过以下指令来获得:

gcc -E source_file.c -o output_file.i
  • -E 选项告诉GCC在预处理阶段之后停止编译过程,不进行编译、汇编和链接。
  • source_file.c 是你的C语言源代码文件。
  • -o output_file.i 指定预处理后的输出文件名。

以下是示例:

如果我们的源代码为:

#include <stdio.h>

int main() {
    printf("Hello World!\n");
    return 0;
}

这里使用上面的指令可以产生 *.i 文件,这时这个文件可能会有很多代码,这是因为:生成的文件将包含stdio.h头文件的内容。

因此,即使你的源代码只有几行,.i文件也可能包含数百行甚至数千行代码。这是正常现象,因为.i文件旨在展示预处理后的完整代码,包括所有被包含的头文件。

2)编译(Compilation):

  • 将预处理后的源代码转换成汇编语言代码。
  • 进行语法分析、语义分析、优化等。
  • 生成汇编代码文件。

如果我们使用gcc来编译代码,这一步生成的结果我们可以通过以下指令来获得:

gcc -S source_file.c -o output_file.s
  • -S 选项告诉GCC在编译阶段之后停止,不进行汇编和链接,而是生成汇编代码。
  • source_file.c 是你的C语言源代码文件。
  • output_file.c 是生成的汇编文件

3)汇编(Assembly):

  • 将汇编代码转换成机器语言指令。
  • 生成目标文件(通常是.obj.o文件,Windows下是.obj,Linux下是.o)。

这个阶段生成的文件是二进制格式的,但还不是完整的可执行程序。

如果我们使用gcc来编译代码,这一步生成的结果我们可以通过以下指令来获得:

gcc -c source_file.c -o output_file.o
  • -c 选项告诉GCC在汇编阶段之后停止,不进行链接,而是生成目标文件。
  • source_file.c 是你的C语言源代码文件。
  • output_file.c 是生成的目标文件。

4)链接(Linking):

  • 将一个或多个目标文件与链接库文件链接在一起。
  • 解决外部符号引用。
  • 生成最终的可执行文件(在Windows上通常是.exe文件,在Unix/Linux上没有扩展名)。

链接器解决外部符号引用,合并代码段和数据段,并可能进行地址重定位。

如果我们在Windows系统上使用gcc来编译代码,这一步生成的结果我们可以通过以下指令来获得:

gcc source_file.c -o output_file.exe
  • source_file.c 是你的C语言源代码文件。
  • -o output_file.exe 选项告诉GCC将生成的可执行文件命名为 output_file.exe。如果你不指定-o选项,GCC将使用默认名称,通常是 a.exe

翻译环境的输出是一个可以在特定操作系统上运行的可执行文件:

2、翻译环境详细介绍

对于前三个步骤,如果一个项目中有多个源文件,那每个源文件都会单独被编译器执行上面的三个步骤,然后产生三个对应的目标文件。

然后第四步链接,则会将多个目标文件与链接库文件链接在一起。这里的链接库就是一些动态和静态库。

在Visual Studio中,编译器是cl.exe,链接器是link.exe。

对于前面的三个步骤,由于它们都是由编译器完成,所以可以合称为一个步骤,合称为编译,第四步由链接器完成,为单独一个步骤——链接。

1)预处理 / 预编译

#include <stdio.h>

#define PI 3.14

int main() {
    printf("Hello World!\n");
    printf("%d",PI);
    //注释注释注释
    return 0;
}

上面使我们的源代码,使用指令生成 .i 文件之后,我们可以看到 .i 文件的部分内容:

int main() {
    printf("Hello World!\n");
    printf("%d",3.14);

    return 0;
}

可以发现这里发生了宏替换,宏定义被删除,宏被替换为实际的代码,还发生了删除注释,以及头文件包含,将头文件的内容插入到源代码中(由于头文件的内容太多,以下有部分截图)。

2)编译 

将预处理后的源代码转换成汇编语言代码。进行语法分析,词法分析,语义分析,优化等。

①词法分析(Lexical Analysis)

词法分析是编译过程的第一步,它的任务是将源代码(字符流)转换成记号(tokens)流。记号是语言的最小语义单位,比如关键字、标识符、常量、运算符等。词法分析器(lexer或scanner)通过识别字符序列来生成这些记号。

在词法分析阶段,会忽略源代码中的空格、换行等非实质性字符。

②语法分析(Syntax Analysis)

语法分析是编译过程的第二步,它的任务是根据语言的语法规则(通常以文法的形式表示)来分析词法分析器生成的记号流,构建出语法树(syntax tree)或抽象语法树(abstract syntax tree, AST)。

语法分析器(parser)使用上下文无关文法(Context-Free Grammar, CFG)来描述语言的语法结构。在分析过程中,如果记号流不符合语法规则,编译器会报告语法错误。

③语义分析(Semantic Analysis)

语义分析是编译过程的第三步,它的任务是检查语法树是否符合语言的语义规则。语义分析器会进行类型检查,确保操作符和操作数之间的类型匹配,以及检查变量是否在使用前被声明等。

语义分析的结果通常是一个带有语义信息的抽象语法树,或者是一个中间代码表示,如三地址码(three-address code)。

④优化

优化可以发生在不同的层次,包括局部优化、循环优化和全局优化等。优化技术包括常量折叠、公共子表达式消除、死代码消除、循环不变量外提等。

补充

C语言的符号汇总(也称为符号表的构建)发生在编译阶段。在这个阶段,编译器会创建并维护一个符号表(symbol table),这是一个数据结构,用于存储源代码中定义的所有标识符(如变量名、函数名、类型名等)及其相关信息,如类型、作用域、内存地址等。

语义分析阶段是创建符号表的阶段,因为直到语义分析阶段,关于名称的足够信息才得以知晓,从而能够对其进行描述。

但是许多编译器在词法分析阶段就为程序中的各种变量建立了一个表,并在语义分析阶段填充有关符号的更多信息,因为此时对变量的了解更为充分。一个经典的例子来自FORTRAN和Ada语言,它们使用相同的语法来引用函数和数组。在这些语言中,F(2)可能指的是数组F的元素F2,或者是使用参数2计算的函数F的值。对于词法分析器来说,要做出这种区分,就需要添加一些语法和语义分析。

  1. 符号表的构建: 它是一个持续的过程,从词法分析开始,到语义分析阶段得到进一步的完善。

  2. 词法分析阶段: 在词法分析阶段,编译器将源代码分解成一个个的词法单元(tokens),如关键字、标识符、常量、运算符等。在这个阶段,编译器开始识别标识符,并将它们的信息初步记录到符号表中。

  3. 语法分析阶段: 在语法分析阶段,编译器根据语言的语法规则将词法单元组合成语法树。在这个阶段,编译器会进一步处理标识符的声明和定义,更新符号表中的信息。

  4. 语义分析阶段: 语义分析阶段是编译过程中的一个重要步骤,在这个阶段,编译器会检查语法树中的语义错误,如类型不匹配、变量未声明等。同时,编译器会完善符号表中的信息,确保每个标识符的类型、作用域和内存地址等信息都是准确和完整的。

  5. 符号表的作用: 符号表在整个编译过程中都起着至关重要的作用。它不仅用于存储标识符的信息,还用于错误检查、代码生成等后续步骤。在链接阶段,符号表的信息也会被链接器用来解析不同编译单元之间的符号引用。

例如下面的例子:

int main() {
    int a = 5;
    float b = 3.14;
    int sum = a + b;
    return 0;
}

在编译这个代码时,编译器会逐步构建和维护符号表。下面是一个可能的符号表示例,展示了在不同阶段添加的信息:

符号类型作用域内存地址或偏移其他信息
main函数全局0x00400000返回类型:int
a整型变量main 函数栈偏移 -4初始值:5
b浮点型变量main 函数栈偏移 -8初始值:3.14
sum整型变量main 函数栈偏移 -12初始值:未初始化

3)汇编

汇编是将汇编语言代码转换成机器语言代码的过程。生成目标文件(通常是.obj.o文件,Windows下是.obj,Linux下是.o)。这个阶段生成的文件是二进制格式的,但还不是完整的可执行程序。

这一步一般是由汇编器(Assembler)来进行,汇编器一般可以是编译器的一部分。

  • 输入:汇编器的输入是汇编代码,这些代码是由编译器从C语言源代码编译而来的。编译器首先将C语言源代码转换成与特定体系结构相关的汇编代码。

  • 输出:汇编器的输出是目标文件(Object File),它包含了机器语言指令和数据,以及一些必要的链接信息,如符号表和重定位信息。

汇编器也会进行一些简单的语法分析和词法分析,因为虽然汇编语言已经很接近机器语言了,但它仍然不是直接的机器代码,依旧需要通过词法和语法分析来解析和转换。

与上面编译步骤的符号表同样,这里也会创建符号表,但是这里的则是对于汇编中的符号。用于存储程序中定义的所有符号(如标签、变量、函数名等)及其相关信息。

4)链接

链接是编译过程中的一个重要步骤,它将多个目标文件(通常是.o文件)和库文件组合成一个可执行文件。

链接过程包括以下步骤:

  1. 确定目标文件位置:链接器首先决定各个目标文件在最终可执行文件中的位置。
  2. 地址重定向:访问所有目标文件的地址重定义表,对其中记录的地址进行重定向。
  3. 符号解析:遍历所有目标文件的未解决符号表,并在所有导出符号表中查找匹配的符号,填写实现地址。
  4. 生成可执行文件:将所有目标文件的内容写入各自的位置,生成可执行文件。

下面介绍两个关键步骤:符号解析和重定位。

i.重定位

重定位是为了解决多个目标文件中可能存在的地址冲突问题。链接器需要调整每个目标文件中的地址,以确保它们在最终的可执行文件中能够正确地组合在一起。这个过程涉及到修改目标文件中的地址引用,使其指向正确的内存位置。

合并段表

段表(Section Table)记录了程序的代码段、数据段和其他段的信息。每个目标文件都有一个段表,描述了该文件中的各个段(如 .text 段、 .data 段、 .bss 段等)。一般在确定目标文件位置和地址重定向时进行。

合并段表的步骤:

  1. 收集段信息

    链接器读取每个目标文件的段表,收集所有段的信息。
  2. 创建全局段表

    链接器创建一个全局段表,将所有目标文件的段合并成一个统一的段表。
  3. 分配段地址

    链接器为每个段分配内存地址,确保它们在内存中的布局是连续且不重叠的。例如,所有 .text 段合并在一起,所有 .data 段合并在一起。
  4. 更新段偏移量

    链接器根据新的内存地址,更新每个段的偏移量。
ii.符号解析

符号解析的目的是将目标文件中引用的符号(函数和变量)与它们的定义进行匹配。每个目标文件都会提供两个符号表给链接器:

  1. 未解决符号表:列出了目标文件中引用的但尚未找到定义的符号及其对应的地址。
  2. 导出符号表:列出了目标文件中定义的,可以供其他目标文件使用的符号及其在本文件中的地址。

链接器会根据未解决符号表,在所有目标文件的导出符号表中查找匹配的符号。如果找到匹配的符号,链接器会将该符号的地址填入未解决符号的地址处;如果没有找到,链接器会报错。

符号表的合并

符号表(Symbol Table)存储了所有符号的信息,包括函数名、变量名及其相应的地址。每个目标文件也有自己的符号表。一般在符号解析和生成可执行文件时进行。

符号表合并的步骤:

  1. 收集符号信息

    链接器读取每个目标文件的符号表,收集所有的符号信息。
  2. 创建全局符号表

    链接器将所有目标文件的符号合并到一个全局符号表中。这里需要注意符号的唯一性,避免重复定义的符号。
  3. 解析符号引用

    链接器解析符号引用,将符号的定义和引用进行匹配。对于每个符号引用,链接器在全局符号表中查找对应的定义。

总结

2、运行环境(Execution Environment)

运行环境是程序实际执行的地方。

  1. 程序必须被加载到内存中才能执行。在有操作系统的环境中,这个过程通常由操作系统自动完成。在独立的环境中,程序的加载必须由手工安排,或者通过将可执行代码预先存储在只读内存(ROM)中来实现。

  2. 程序的执行从调用main函数开始。这是大多数编程语言中程序执行的入口点。

  3. 开始执行程序代码。此时,程序将使用堆栈(stack)来存储函数的局部变量和返回地址。程序还可以使用静态(static)内存,存储在静态内存中的变量在整个程序执行过程中一直保留它们的值。

  4. 程序终止。这可能是正常的,比如main函数执行完毕;也可能是异常的,比如由于错误或异常导致的提前终止。

在运行环境中,程序的行为包括:

  • 加载:程序的机器代码被加载到内存中,准备执行。
  • 执行:CPU按照程序的指令顺序执行代码,完成程序逻辑。
  • 输入输出:程序与用户、文件系统、网络等进行数据交换。
  • 错误处理:程序在运行时可能会遇到错误,需要进行错误处理和异常恢复。
  • 资源管理:程序需要管理内存、文件句柄、网络连接等资源。
  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是古老而长青的编程语言,它具备了现代程序设计的基础要求,它的语法是很多其他编程语言的基础,在系统程序、嵌入式系统等领域依然是无可替代的编程语言,在各类编程语言排行榜上常年占据前两名的位置。 本课程是零基础的编程入门课,是后续的操作系统、编译原理、体系结构等课程的基石。 —— 课程团队 课程概述 程序设计是一门基础课程。对于计算机相关专业而言,程序设计是专业基础知识,是进一步学习其他专业知识的第一步阶梯;对于非计算机专业而言,程序设计的学习有助于理解计算机的能力所在,理解哪些是计算机擅长解决的问题,怎样的方式方法是计算机擅长的手段,从而能更好地利用计算机来解决本专业领域内的问题。 C语言是古老而长青的编程语言,它具备了现代程序设计的基础要求,它的语法是很多其他编程语言的基础,在系统程序、嵌入式系统等领域依然是无可替代的编程语言,在各类编程语言排行榜上常年占据前两名的位置。 对于非计算机专业的学生,学习本课程的主要目的是掌握程序设计的基本方法,C语言是教学媒介。但是对于计算机专业的学生,本课程是向后续的计算机组成、操作系统、编译原理、体系结构等课程前进的基石,对于C语言本身甚至程序设计语言基础原理的深入理解都是应该掌握的。 本课程是零基础的入门课程,完成本课程之后,就能具有初步的运用C语言编写程序的能力。要想完整的学习C语言,还需要进一步学习本课程的后续课程——《C语言程序设计进阶》。 程序设计是实践性很强的课程,该课程的学习有其自身的特点,听不会,也看不会,只能练会。你必须通过大量的编程训练,在实践中掌握编程知识,培养编程能力,并逐步理解和掌握程序设计的思想和方法。在这里所提供的,只是基础的知识讲解,要想学会编程,还需要更多时间的投入和努力。 为了学习编程,你需要有一台计算机,安装必要的编程软件。无论是MS Windows、Mac OS X还是Linux,都有适合C语言编程的软件。如果搞不定自己电脑上的编程软件,我们也会提供在网页中编写、运行C语言程序的方法。 课程大纲 01 程序设计与C语言 课时 1 计算机和编程语言 2 C语言 3 第一个程序 02 计算 课时 1 变量 2 数据类型 3 表达式 4 在线评判系统 03 判断与循环 课时 1 判断 2 循环 04 进一步的判断与循环 课时 1 逻辑类型和运算 2 级联和嵌套的判断 3 多路分支 4 循环的例子 5 判断和循环常见的错误 05 循环控制 课时 1 循环控制 2 多重循环 3 循环应用 06 数组与函数 课时 1 数组 2 函数的定义与使用 3 函数的参数和变量 4 二维数组 07 数组运算 课时 1 数组运算 2 搜索 3 排序初步 08 指针与字符串 课时 1 指针 2 字符类型 3 字符串 4 字符串计算 预备知识 作为第一门编程课,本课程可以零基础学习。大学计算机(大学计算机基础、计算思维导论)等课程对于理解本课程的部分内容有帮助,但这些课程的学习不是必须的。 证书要求 课程的总分达到60分以上,可以获得本课程的合格证书。 课程的总分达到85分以上,可以获得本课程的优秀证书。 参考资料 何钦铭、颜晖,《C语言程序设计(第3版)》,高等教育出版社,2015年,ISBN 978-7-04-043128-3 颜晖、张泳,《C语言程序设计实验与习题指导(第3版)》,高等教育出版社,2015年,ISBN 978-7-04-043563-4 常见问题 Q:我需要特殊版本的计算机吗? A:任何计算机都可以用于C语言编程,包括但不限于各个版本的MS Windows、各个版本的Mac OS X、各种发行版本的Linux。有的手机有app也能实现C语言编程,不过我们不推荐那么小的屏幕。 Q:我需要安装特定的软件才能做这门课的作业吗? A:任何C语言开发工具都可以。课程演示会使用Dev C++及命令行环境,但是你使用任何其他开发工具都不会影响做作业的正确性。可以使用的其他工具包括但不限于MS Visual Studio、C-Free等。 Q:每周会发布多少时间的视频?我需要花多少时间来学习? A:我们按照90分钟的授课时间来设计课程。但是发布的视频肯定小于90分钟,一般在50到70分钟左右。因为在线下上课时,老师可能会回顾一下上周的内容、某些内容会看学生的反应重复几遍、会当堂问做一些提问,以及在各种软件等教学工具之间切换,这些都要花点时间,而这些在线上课程中都不存在了,所以视频的时间不会正好是90分钟。但是一般我们设计课程需要1:1.5到1:2的课后学习时间,这包括预习、作业、练习和复习的时间,所以每周大约需要花费三到五小时的时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值