首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >hello程序是如何被编译出来的?

hello程序是如何被编译出来的?

作者头像
编程珠玑
发布于 2019-09-03 03:46:08
发布于 2019-09-03 03:46:08
80800
代码可运行
举报
文章被收录于专栏:编程珠玑编程珠玑
运行总次数:0
代码可运行

前言

hello程序几乎是我们每个人学习C语言写的第一个程序,但是它是如何从.c文本变成可以打印出”hello world“的可执行文件的呢?本文将简单介绍其过程。

Hello World

hello world程序我们再熟悉不过:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*include head file*/
#include<stdio.h>
/*the main function*/
int main(int argc,char *argv[])
{
    printf("Hello World!\n");
    return 0 ;
}

编译并运行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 gcc -o helloWorld helloWorld.c 
 ./helloWorld
 Hello World!

整个过程一气呵成,但是实际上上面的过程并非像看起来那么简单。它可以大体分为4个步骤:预处理,编译,汇编,链接。接下来我们一一简单介绍这四个步骤做了什么。

预处理

预处理主要是处理源代码中以#开头的指令(#pragma 除外),例如本文hello world程序中的#include,预处理之后会将stdio.h的内容插入到预处理指令的位置。 想要只生成预处理之后的内容,可以使用下面的方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -E -o helloWorld.i helloWorld.c #-E参数表示只进行预处理

生成的helloWorld.i即为预处理之后的内容,有兴趣的可以打开文件查看里面的内容,会发现stdio.h的位置被其实际内容所替代。预处理之后,注释内容也会被删除,宏定义会被展开。

编译

预处理之后就需要对生成的预处理文件进行词法分析,语法分析,语义分析,最终产生汇编代码文件,说白点可以简单理解为将C代码“翻译”成汇编代码。该过程是核心同时也是较复杂的一个过程。我们可以通过命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -S -o helloWorld.s helloWorld.c #-S参数表示只到生成汇编为止
cat helloWorld.s
    .file   "helloWorld.c"
    .section    .rodata
.LC0:
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits

上面的内容即为编译之后得到的汇编代码。

汇编

汇编是将汇编代码翻译成机器可执行的指令,生成目标文件。整个过程较为简单,几乎只是按照汇编指令和机器指令进行一一翻译。我们可以用下面的命令获得汇编后的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc  -o  helloWorld.o   -c helloWorld.c
od helloWorld.o  #查看二进制内容
0000000 042577 043114 000402 000001 000000 000000 000000 000000
0000020 000001 000076 000001 000000 000000 000000 000000 000000
0000040 000000 000000 000000 000000 001260 000000 000000 000000
0000060 000000 000000 000100 000000 000000 000100 000015 000012
0000100 044125 162611 101510 010354 076611 044374 072611 137760
(其他内容未显示)

链接

链接是以某种方式将各个目标文件整个在一起,生成最后的可执行文件。我们的hello程序中调用了printf函数,但是并不存在于helloWorld.o中,而是存在于libc.so或libc.a中,因此我们需要通过链接将它们融合在一起。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -o helloWorld helloWorld.c

执行上面的命令之后,就得到了我们的helloWorld程序了,在linux下,它是一种ELF格式的文件,后面的文章我们会更多地介绍到。 我们通过ldd命令看到helloWorld程序链接了系统的库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ldd helloWorld
    linux-vdso.so.1 =>  (0x00007ffe9ef11000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d9f038000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f0d9f402000)

有兴趣的也可以尝试一下,如果删除系统中的libc.so库(记得事先备份),发现能够编译过,却在最后链接失败。

总结

  • 我们总结整个编译过程大致如下:

而正是由于整个编译过程分阶段进行,我们可以看到不同类型的问题在不同阶段出现并且有先后顺序。正因如此,链接问题在编译的最后阶段才会出现。

  • gcc编译系统本身调用了很多其他相关工具,可以加上--verbose观察其详细编译过程,发现gcc命令调用了预处理器,编译器,汇编器,链接器等命令。

本文只是粗略介绍其整个过程,更多地了解编译过程能够帮助我们优化代码、处理令人困扰的链接问题或避免安全漏洞,本文不展开介绍具体的编译过程,有兴趣的同学可以阅读《编译原理》。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux 程序编译过程的来龙去脉
大家肯定都知道计算机程序设计语言通常分为机器语言、汇编语言和高级语言三类。高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类,一种是编译型语言,例如C,C++,Java,另一种是解释型语言,例如Python、Ruby、MATLAB 、JavaScript。
刘盼
2018/09/25
3.2K0
Linux 程序编译过程的来龙去脉
Hello World背后的故事:如何在Linux上编译C语言程序
C语言的经典程序“Hello World”并不难写,很多朋友都可以闭着眼将它写出来。那么编译一个“Hello World”到底经历了怎样的过程呢?
PP鲁
2020/09/15
2.1K0
Hello World背后的故事:如何在Linux上编译C语言程序
gcc 编译一个应用程序的四个过程
还是 2015 年学过的知识,这么久不用忘差不多了。本文主要记录一下方便以后查阅并加深印象。gcc 编译一个程序的四个过程分别是 预处理->汇编->编译->链接,预处理一般是导入一些头文件的信息及一些宏的替换等等,汇编是将代码编译为汇编代码,真正到编译过程才是把汇编代码编译为二进制的文件,最后链接是链接一些函数所需的库文件。以下是分布执行对应步骤的命令。
我与梦想有个约会
2023/10/21
3290
gcc 编译一个应用程序的四个过程
叙述 C语言编译
工作原因有时候会用python写写测试工具,感受到其快速实现应用的便利,但由于偏底层开发,主力语言依然是C。对于开发语言没有什么优劣概念,在特定的情景下哪种实现更佳就用哪种,工具合适才是最好的。
orientlu
2018/09/13
2K0
叙述 C语言编译
移位溢出
实际项目中需要计算SD卡中某个目录的大小,并判断该目录所占空间是否超过SD卡总容量的一半。 测试过程中经常发现误报,该目录所占空间远小于SD卡容量一半的时候,就上报占用空间过半的事件。 排查发现原来是计算的时候移位导致了溢出。问题代码如下:
coderhuo
2018/08/29
1.4K0
编译、链接到载入、运行的大致过程 ----1. 编译
对于需要编译的编程语言(c, c++, java, c# ...高级语言),源码写完后,是无法直接运行的;需要有 编译,链接的过程才能生成最终可以执行的二进制文件;
qsjs
2020/06/08
5590
gcc编译原理和顺序
gcc -xc++ -lstdc++ -shared-libgcc 这里会声明使用c++标准库
opencode
2022/12/26
4010
程序的机器级表示
预处理阶段:预处理器cpp根据编译文件以“#”开头的命令,读取系统头文件stdio.h(.h结尾的表示头文件,.c表示可执行文件)的内容,并把它插入到程序文本中,得到一个新的文件。
_春华秋实
2019/02/22
7120
程序的机器级表示
一个奇怪的链接问题
链接是代码生成可执行文件中一个非常重要的过程。我们在使用一些库函数时,有时候需要链接库,有时候又不需要,这是为什么呢?了解一些链接的基本过程,能够帮助我们在编译时解决一些疑难问题。比如,下面就有一种奇怪的现象。
编程珠玑
2019/09/02
1.8K0
C语言编译过程
预编译结果解释 # linenum filename flags 分别对应行号、文件、标识。 flag对应的含义
用户2929716
2018/08/23
2.1K0
转载:【AI系统】GCC 主要特征
GCC(GNU Compiler Collection,GNU 编译器集合)最初是作为 GNU 操作系统的编译器编写的,旨在为 GNU/Linux 系统开发一个高效的 C 编译器。其历史可以追溯到 1987 年,当时由理查德·斯托曼(Richard Stallman)创建,作为 GNU 课程的一部分。
聊月夜以予星辰
2024/12/11
1630
转载:【AI系统】GCC 主要特征
【AI系统】GCC 主要特征
GCC(GNU Compiler Collection,GNU 编译器集合)最初是作为 GNU 操作系统的编译器编写的,旨在为 GNU/Linux 系统开发一个高效的 C 编译器。其历史可以追溯到 1987 年,当时由理查德·斯托曼(Richard Stallman)创建,作为 GNU 课程的一部分。
用户11307734
2024/11/27
1980
gcc命令的常用选项_curl常用命令及参数
gcc是GUN C和C++编译器,我们通常使用GCC时,编译器会依次做如下工作:preprocess(预处理),compilation(编译),assembly(汇编),link(链接)。gcc提供了一些选项参数能够让编译器停在某个过程(如编译过程),比如 -c选项表示只走到“汇编”这一步,生成的是汇编后的目标文件。本文主要介绍gcc常用的选项参数及其作用。 1.-c 对源代码进行预处理、编译、汇编,但不执行链接,产生的是源代码的目标文件(*.o)
全栈程序员站长
2022/11/04
7470
【专业技术】编译器的工作原理
源码要运行,必须先转成二进制的机器码。这是编译器的任务。 比如,下面这段源码(假定文件名叫做test.c)。 #include <stdio.h>int main(void){ fputs("Hello, world!\n", stdout); return 0;} 要先用编译器处理一下,才能运行。 $ gcc test.c $ ./a.out Hello, world! 对于复杂的项目,编译过程还必须分成三步。 $ ./configure $ make $ make install 这些
程序员互动联盟
2018/03/14
8250
【专业技术】编译器的工作原理
编译器的工作过程
源码要运行,必须先转成二进制的机器码。这是编译器的任务。 比如,下面这段源码(假定文件名叫做test.c)。 #include <stdio.h> int main(void) { fputs("Hello, world!\n", stdout); return 0; } 要先用编译器处理一下,才能运行。 $ gcc test.c $ ./a.out Hello, world! 对于复杂的项目,编译过程还必须分成三步。 $ ./configure $ make $ make install 这
ruanyf
2018/04/12
8670
编译器的工作过程
【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可执行文件;
韩曙亮
2023/03/27
7960
x64架构下Linux系统函数调用
push指令将数据压栈。具体就是将esp(stack pointer)寄存器减去压栈数据的大小,再将数据存储到esp寄存器所指向的地址。
Orlion
2024/09/02
3900
x64架构下Linux系统函数调用
C/C++未定义行为
下面是一段代码,这段代码中有标准未定义的行为。代码如下: #include<iostream> using namespace std; int main() { int j = 0;
zy010101
2020/04/16
4.3K0
有了 for 循环,为什么还要 while(1)?
有读者问题了类似这样的问题:while(1) 和 for(;;)它们不都是无限循环吗,作用应该一样啊,它们到底有什么区别?
搜云库技术团队
2023/03/15
7460
有了 for 循环,为什么还要 while(1)?
【C语言系列】C语言编译流程分析
前几天看了《程序员的自我修养——链接、装载与库》中的第二章“编译和链接”,主要根据其中的内容简单总结一下C程序编译的过程吧。 我现在一般都是用gcc,所以自然以GCC编译hellworld为例,简单总结如下。 hello.c源代码如下: #include <stdio.h> int main() { printf(“Hello, world.\n”); return 0; } 通常我们使用gcc来生成可执行程序,命令为:gcc hello.c,默认生成可执行文件a.out 其实编译(包括链接)的命令:g
程序员互动联盟
2018/03/16
11.2K0
【C语言系列】C语言编译流程分析
相关推荐
Linux 程序编译过程的来龙去脉
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档