编译和链接

1.翻译环境和运行环境

在ANSI C中,存在两个不同的环境:翻译环境和运行环境

  • 翻译环境 :我们在编译器编写出的代码都是文本信息,机器是看不懂的,翻译环境主要是源代码转化为可执行的机器指令的地方
  • 运行环境:实际执行代码的地方

2 .翻译环境

翻译环境主要是源代码转化为机器可执行指令的地方

这个过程又包含了:编译链接两个组要过程

编译又可以分为:预编译(预处理),编译,汇编三个阶段

2.1预编译(预处理)

在预处理阶段,源文件和头文件会被处理成为.i为后缀的⽂件。.i为后缀的文件是一个中间文件,在后面会被删除,它里面通常包含文本信息
要想查看预编译后的 .i 文件,可以使用c编译器(如gcc)的 -E 选项。例如:
gcc  -E   文件名.c    -o   文件名.i
这将会生成一个名为:文件名 .i 的文件,它包含了预编译后的源代码
预编译做了什么?:

1.宏替换

将所有的 #define 删除,并展开所有的宏定义

预处理器通常会找源码中的宏定义,将他们替换成相应的值或表达式。这包含使用#difine定义的常量,宏函数

#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))

double a = 5.0;
double b = PI * b * bs;
int x = 10, y = 20;
int max_val = MAX(x, y);

PI会被替换成:3.14159,Max(x,y)会被替换成:((x) > (y) ? (x) : (y))


2.条件编译

处理所有的条件编译指令,如: #if#ifdef#elif#else#endif


3.文件包含

预处理器会将#include指令指向的文件内容插入到源代码中,这个过程是递归进行的,也就是说被包含的头⽂件也可能包含其他⽂件

#include <stdio.h>
#include "myheader.h"

<stdio.h>是标准库头文件,而"myheader.h"是用户自定义的头文件。预处理器会查找这些文件,并将它们的内容复制到#include指令的位置。


4.删除所有注释

预处理器会删除源代码中的所有注释,无论是单行注释(//)还是多行注释(/* ... */)


5.添加⾏号和⽂件名标识

预处理器可以处理#line指令,它用于改变当前行号和文件名,这在某些情况下对于错误处理和调试是有用的。

例如:程序语法错误的时候,错误列表不仅输出了错误信息还输出了行号


6.处理其他预处理指令

还有一些其他的预处理的指令也会被一起处理

例如:#undef(用于取消宏定义) ,#pragma(用于提供特定的编译器指令)

经过预处理后的.i⽂件中不再包含宏定义,因为宏已经被展开。并且包含的头⽂件都被插⼊到.i⽂件
中。所以当我们⽆法知道宏定义或者头⽂件是否包含正确的时候,可以查看预处理后的.i⽂件来确认。

 2.2编译

编译阶段就是将预处理后的文件经过“词法分析,语法分析,语义分析,优化,代码生成”,一系列的操作生成相应的汇编文件

2.2.1词法分析

将源代码程序被输⼊扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成⼀系列 的记号(关键字、标识符、字⾯量、特殊字符等)
例如: array [index] = (index+ 4 )*( 2 + 6 );

上⾯程序进行词法分析后得到了16个记号 :

 2.2.2语法分析

接下来语法分析器,将扫描产生的记号,抽象成语法树(AST)

2.2.3语义分析

语义分析器来完成语义分,检查语法树(AST)的语义正确性,确保代码遵循语言规则
例如:类型匹配,类型的转换,作用域解析等

2.3汇编 

汇编器就是将汇编码进一步转化成机器指令的地方,几乎每一条汇编码都对应一条机器指令。

汇编器根据据汇编指令和机器指令的对照表⼀⼀的进⾏翻译,也不做指令优化。

汇编的命令如下:
gcc -c test.s -o test.o

2.4链接 

链接是⼀个复杂的过程,链接的时候需要把多个目标文件(.o   .obj等文件)和必要的库文件组合成一个单一的可执行文件。
链接过程主要包括:地址和空间分配,符号解析和重定位等这些步骤。
链接解决的是⼀个项⽬中多⽂件、多模块之间互相调⽤的问题。
符号解析:链接器需要找到目标文件中引用的所有函数和变量的定义。如果定义在当前目标文件中,链接器将其内部链接;如果定义在其他目标文件或库中,链接器需要找到相应的定义,并确定其在最终程序中的地址。

重定位:链接器需要确定每个目标文件在内存中的位置,并修正目标文件中所有绝对地址和相对地址的引用,使得它们在最终的执行文件中是正确的。
 

 3.运行环境

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

相关推荐

  1. 编译

    2024-04-07 10:20:05       54 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-07 10:20:05       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-07 10:20:05       101 阅读
  3. 在Django里面运行非项目文件

    2024-04-07 10:20:05       82 阅读
  4. Python语言-面向对象

    2024-04-07 10:20:05       91 阅读

热门阅读

  1. leetcode594-Longest Harmonious Subsequence

    2024-04-07 10:20:05       32 阅读
  2. redis bigKey问题

    2024-04-07 10:20:05       37 阅读
  3. Docker 中运行一个容器并查看其日志

    2024-04-07 10:20:05       38 阅读
  4. flinkCDC

    flinkCDC

    2024-04-07 10:20:05      31 阅读
  5. leetcode599-Minimum Index Sum of Two Lists

    2024-04-07 10:20:05       25 阅读
  6. 蓝桥杯day19刷题日记--P8686 修改数组

    2024-04-07 10:20:05       36 阅读
  7. 虚拟内存知识详解

    2024-04-07 10:20:05       39 阅读
  8. 试除法求素数

    2024-04-07 10:20:05       35 阅读
  9. Git的学习,从入门到入土

    2024-04-07 10:20:05       37 阅读