c语言执行Hello World背后经历的步骤

目录
  • 预编译
  • 编译
  • 汇编
  • 链接

计算机的世界,就从hello,world开始吧!

#include <stdio.h>
int main()
{
	printf("Hello World\n");
  return 0;
}

“Hello World”,对于好兄弟们来说,都很熟悉吧,大学第一课、编程语言书本的第一个dmeo,基本都是用这个作为引子,这次我们也从hello,world开始进入计算机的世界遨游吧!

刚开始学这些东西的时候,比如用VC++, 都是鼠标点点,直接出来黑窗口,可以看到我们的执行结果,却不知,这一系列的背后,隐匿了很多我们不知道的细节,而这些东西都让VC++这类的集成开发环境帮我们做了(当很多东西被封装成简单的API给我们使用的时候,也同时证明了我们的可替代性变得越来越高,那怎么办?好好读这篇文章:)),有的工作了好几年的,也不见得知道Hello,World的执行过程,这次我们把它搞懂。

先来看一下整个C程序从写完代码,到执行所经历的步骤:

当我们在linux上输入如下指令的时候,VC++或者别的C的开发环境,就会在背后帮我们进行上面的动作。

$gcc  hello.c
$./a.out

预编译

通过预编译器,生成".i"文件

这个过程主要是处理源代码文件中以“#”开始的预编译指令,比如:"#include",“define”

对于我们的hello程序来说,会处理#include指令,将被包含的stdio.h文件插入到第一行我们的#include指令的位置上,但是我们的stdio.h可能还包含的别的#include,所以这个过程是递归进行的

如果我们有宏定义,比如#define,会展开所有的宏定义,比如:#define PI 3.14, 在预编译步骤中,会将#define删除,然后将所有PI替换成3.14

如果我们在代码中存在注释的时候,还会将注释进行删除,可见注释并不会对我们的代码产生什么影响

如何查看预编译后的文件呢?

$gcc -E hello.c -o hello.i
  • -E:表示只进行预编译
  • -o:指定要生成的结果文件,后面就是结果文件的名字

经过预编译之后的.i文件中不会包含任何宏定义,也就是#define,因为已经被替换,所以当无法判断宏定义是否正确或者头文件是否包含正确时,可以查看预编译后的文件来确定问题

#define PI 3.14
int main()
{
	double d = PI;
  return 0;
}
--------预编译之后---------
int main()
{
  double d = 3.14;
  return 0;
}

编译

编译的过程是把预处理文件进行:词法分析->语法分析->语义分析->源代码生成->目标代码的生成和优化

整个过程如下:

其结果是产生.s的汇编文件

上面的过程相当于执行了:

$gcc -S hello.i -o hello.s

也可以用命令ccl来完成,路径是/usr/lib/gcc/x86_64-linux-gnu/7/cc1,这个命令是将预编译和编译封装了起来

$/usr/lib/gcc/x86_64-linux-gnu/7/cc1 hello.c

其实gcc 的-S命令就是调用的cc1这个命令,所以gcc这个命令就是这些程序的包装,这些成比如:cc1,ld,as这些其实都是程序,gcc会根据不同的参数去调用不同的程序,相当于在外面加了一层

来看下编译的详细过程:

词法分析

这个过程会产生token,听着token感觉好高大上,其实也就那么回事,通俗点来说,给程序中的所有的符号进行分类,而这个分类都有什么呢?比如:标识符、左括号、右括号、加号、乘号、数字、赋值、左右方括号

arr[i] = (i + 1) * (2 + 3)

对上面语句进行分类就是:arr、i是标识符,1、2、3都属于数字,有加号、还有乘号、还有左右括号、左右方括号和赋值,就是这么简单的分类,这里面的每个符号,都表示一个token。

语法分析

语法分析的结果是生成语法树,一听很懵逼是吧,听我给你慢慢道来,先来一句总结的话,语法树怎么生成的?可以这么理解:就是以运算符为根节点,操作数为孩子节点,将语句根据运算符的优先级从右到左,将树从下到上构造成的。没听懂吗? 上图

  • 按照运算符的优先级,应该先计算()和[]中内容,按照我们的运算符为根节点的说法,所以以i和1为孩子节点,以+为根节点,以2和3为孩子节点,以+为根节点,[]为根节点,arr和i为孩子节点
  • 然后以*为根节点,上述生成的两个节点看成一个整体作为*的根节点,赋值左边的[]也是以相同的逻辑形成生成一个子树
  • 最后以=作为根节点,将上面步骤生成的两个根节点看成一个整体,形成一个语法树

总结:

  • 语法树是以表达式为节点的树,C中一个语句就是一个表达式,而一个复杂的语句又是很多表达式的组合,比如我们的语句中有:赋值表达式、加法表达式、乘法表达式、数组表达式。
  • 在上述的图中,叶子节点都以黄色标识出来,可以看到符号和数字是最小的表达式
  • 同时在语法分析的同时,运算符的优先级也被确定了下来,()和[]一样高,()比*优先级高,*比+号优先级高
  • 在语法分析过程中,如果出现了表达式不合法,比如括号不匹配等,编译器会报错误

语义分析

那么语义分析阶段主要做什么事情呢?

语法分析,只是完成了表达式语法层面的分析,并不知道这个语句的真正意义,比如说两个指针做乘法运算,语法分析是分析不出来的。

语义分析包括:静态语义和动态语义,静态语义就是在编译期间可以确定的语义,比如将浮点数赋值给整型的类型转换,动态语义就是运行时才能确定的语义,比如0作为除数。

来个case:如果将一个浮点数赋值给一个指针,语义分析阶段就会出错。

语义分析的结果就是:整个 语法树的表达式,都被标识了类型

中间语言生成

编译器在源代码级别会有一个优化的过程,比如我们上述的表达式2 + 3就可以被优化成5:

直接在语法树上做优化比较困难,所以源码优化器将整个语法树转化成中间代码,它是语法树的顺序表示,此时的中间代码和目标机器和运行时环境还是无关的,中间代码使编译器可以分为前端和后端

目标代码生成和优化

代码生成器将中间代码转换成目标机器码:这个过程依赖于目标机器,不同的机器有不同的字长、寄存器等,此时生成的就是汇编代码了

movl i, $ecx
addl $4, %ecx
....

目标代码优化器对目标代码进行优化,比如选择一个合适的寻址方式等

汇编

汇编是将汇编代码转换成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令,所以这个过程,根据汇编指令和机器指令的对照表,一一分析就可以了。

上面的过程相当于执行了

$gcc -c hello.c -o hello.o

或者

$gcc -c hello.s -o hello.o

或者

$as hello.s -o hello.o

又一次验证了上面的结论,gcc命令对as程序的封装

汇编的结果生成的.o文件叫做目标文件

链接

到目前位置,完成了编译的整个过程,到现在位置,还没有为程序中的变量分配地址,那么什么时候分配地址呢?假设已经分配了地址,那么我们有可能在引用了别的文件中的变量或者函数,那么此时怎么为他们分配地址呢?所以肯定不是在之前分配地址的。

这个过程在链接阶段才能确定,定义在其他文件的全局变量和函数在最终运行时的绝对地址都要在最终链接时才能确定,所以编译器将一个源码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。

链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块之间能够正确链接,这里所有的模块之间的相互引用是指全局变量的相互引用和函数的相互调用,其实链接的工作就是把一些指令对其他符号的地址的引用加以修正

链接过程主要包括:

  • 地址和空间分配
  • 符号决议(静态链接)
  • 重定位

什么是静态链接呢?

源代码文件经过编译器后生成目标文件,目标文件和库一起链接成可执行文件,这里的库是运行时库,库是一组目标文件的包,就是一些常用的代码编译成目标文件后打包存放

比如有两个文件A.c 和B.c A中使用了B的函数foo()和变量var, 由于每个模块都是单独编译的,所以在编译阶段并不知道函数foo和变量var的地址,所以就将他们地址暂时设置成0,等待链接器将目标文件A和B链接起来的时候再修改正,这个修正的过程叫做重定位,整个过程就是静态链接的基本过程。

以上所述是小编给大家介绍的c语言执行Hello World背后经历的步骤,希望对大家有所帮助。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 6个变态的C语言Hello World程序

    下面的六个程序片段主要完成这些事情: 输出Hello, World 混乱C语言的源代码 下面的所有程序都可以在GCC下编译通过,只有最后一个需要动用C++的编译器g++才能编程通过. hello1.c #define _________ } #define ________ putchar #define _______ main #define _(a) ________(a); #define ______ _______(){ #define __ ______ _(0x48)_(0x65

  • C语言之没有main函数的helloworld示例

    几乎所有程序员的第一堂课都是学习helloworld程序,下面我们先来重温一下经典的C语言helloworl 复制代码 代码如下: /* hello.c */  #include <stdio.h>    int main()  {      printf("hello world!\n");      return 0;  } 这是一个简单得不能再单的程序,但它包含有一个程序最重要的部分,那就是我们在几乎所有代码中都能看到的main函数,我们编译成可执行文件并查看符号表,

  • C语言菜鸟基础教程之Hello World

    首先,需要一款C语言的编译器,可以使用在线编译器,也可以在本地安装编译器,比如Mac电脑可以安装Xcode,PC可以安装Dev C++. 编写第一个程序:Hello World! #include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n"); return 0; } 运行结果: Hello, World! 程序分析: (1) 这里stdio.h是一个头文件.代表stantard inp

  • c语言执行Hello World背后经历的步骤

    目录 预编译 编译 汇编 链接 计算机的世界,就从hello,world开始吧! #include <stdio.h> int main() { printf("Hello World\n"); return 0; } "Hello World",对于好兄弟们来说,都很熟悉吧,大学第一课.编程语言书本的第一个dmeo,基本都是用这个作为引子,这次我们也从hello,world开始进入计算机的世界遨游吧! 刚开始学这些东西的时候,比如用VC++, 都是鼠标

  • Go语言执行系统命令行命令的方法

    本文实例讲述了Go语言执行系统命令行命令的方法.分享给大家供大家参考.具体如下: 执行Go代码时可以附加参数,包括要执行的命令和给命令的参数 复制代码 代码如下: package main import (   "os"   "os/exec"   "fmt"   "flag"   "strings" ) func main() {   command := flag.String("cmd&qu

  • go语言执行windows下命令行的方法

    本文实例讲述了go语言执行windows下命令行的方法.分享给大家供大家参考.具体如下: 在golang里执行windows下的命令行,例如在golang里面调用 del d:\a.txt 命令 复制代码 代码如下: package main import(     "fmt"     "os/exec" ) func main(){       c := exec.Command("cmd", "/C", "del

  • go语言执行等待直到后台goroutine执行完成实例分析

    本文实例分析了go语言执行等待直到后台goroutine执行完成的用法.分享给大家供大家参考.具体如下: 复制代码 代码如下: var w sync.WaitGroup w.Add(2) go func() {     // do something     w.Done() } go func() {     // do something     w.Done() } w.Wait() 希望本文所述对大家的Go语言程序设计有所帮助.

  • C++/Php/Python 语言执行shell命令的方法(推荐)

    编程中经常需要在程序中使用shell命令来简化程序,这里记录一下. 1. C++ 执行shell命令 #include <iostream> #include <string> #include <stdio.h> int exec_cmd(std::string cmd, std::string &res){ if (cmd.size() == 0){ //cmd is empty return -1; } char buffer[1024] = {0}; s

  • Go语言框架Beego项目搭建的方法步骤

    1,命令行查看 Go 开发包的环境变量配置信息 命令行说明如下: 第 1 行,执行 go env 指令,将输出当前 Go 开发包的环境变量状态. 第 2 行,GOARCH 表示目标处理器架构. 第 3 行,GOBIN 表示编译器和链接器的安装位置. 第 7 行,GOOS 表示目标操作系统. 第 8 行,GOPATH 表示当前工作目录. 第 10 行,GOROOT 表示 Go 开发包的安装目录. 从命令行输出中,可以看到 GOPATH 设定的路径为:C:\Users\Administrator\g

  • 搭建Go语言的ORM框架Gorm的具体步骤(从Java到go)

    目录 [提问] [解答] 1.检查Go的安装 2.安装Gorm 3.安装对应数据库的驱动 4.编写「数据库连接」代码 5.编写「数据Model」代码 6.编写「数据查询」代码 [提问] 如何使用Goland软件,搭建一个ORM框架GORM? [解答] 具体步骤如下: 1.检查Go的安装 在任意目录执行如下命令: go version 若有如下返回,则安装成功:如果报异常,则重新安装golang go version go1.19.1 darwin/arm64 2.安装Gorm 在任意目录执行如下

  • Go语言利用ssh连接服务器的方法步骤

    学习了Go语言后,打算利用最近比较空一点,写一个前端部署工具,不需要每次都复制粘贴的麻烦,需要完成部署的第一步就需要连接远程服务器 打开 ssh server 首先我们想要利用ssh连接服务器的前提是服务器打开了ssh server,ssh 分为client和server端 ,如果打开了client可以连接远程服务器,打开了server就可以被连接. 因为linux网上教程很多,windows比较少,所以这里只写windows版本的, 首先我们一般用Open SSH这个工具打开服务,window

  • go语言实战之实现比特币地址校验步骤

    由公钥生成比特币地址步骤 随机取一个32位随机数作为私钥 利用生产的随机数采用椭圆加密算法生成公钥 计算公钥的sha256哈希值 计算RIPEMD-160哈希值 第4步结果加上版本号(比特币为0x00) 对第5步结果取两次sha256哈希值 取上一步结果的前四个字节 将第7步结果加到第步的结果后面作为校验 利用base58对第8步结果进行变化得到地址 生成地址代码如下 func (w Wallet) GetAddress() []byte { pubKeyHash := HashPubKey(w

  • C语言详解实现猜数字游戏步骤

    目录 前言 一.猜数字游戏是什么 二.使用步骤 总结 前言 本文详细介绍了猜数字游戏的具体实现逻辑 一.猜数字游戏是什么 1.电脑自动生成一个1-100以内的数字 2.我们输入一个我们猜的数字 3.如果我们猜的数字比电脑随机生成的数字大,那么输出(猜大了) 4.如果我们猜的数字比电脑随机生成的数字小,那么输出(猜小了) 5.如此循环往复,猜到正确的数字之后游戏结束 二.使用步骤 1.首先应该打印菜单 代码如下 void menu() { printf("*********************

随机推荐