您的位置:首页 > 其它

一个程序是怎样跑起来的(一)

2016-10-22 18:33 501 查看
找工作期间断断续续看了一些关于程序运行原理的东西,这里做个简单的小结。因为这方面还不熟悉,可能有些东西描述的不准确甚至有纰漏,还希望大家能及时指出。

首先看一下从一个我们编写的源代码程序到一个被电脑运行的程序需要几个步骤:



从这个转换图中可以看出,编译、装载、运行这三个步骤就是本文所描述的核心过程,中间会有部分内容涉及到对程序内容和进程的相关描述。

本文的源代码以main.c,head.h,func.c三个文件为例进行说明。

main.c

#include<stdio.h>
#include"head.h"
int main(){
int a = 12;
int b = 23;
int c = add(a, b);
printf("%d\n",c);
return 0;
}


head.h

int add(int a, int b);


fun.c

int add(int a, int b){
return (a + b);
}


1、编译

再解释编译的过程之前,首先说明一下为什么要有编译这个过程。现在的计算机在执行的时候,其实只能识别机器语言(二进制形式),除了机器语言以外的东西计算机是完全看不懂的。当我们用高级语言编写好程序后,为了让我们的程序能够正常的被机器识别和执行,需要一个转换的过程来把我们用高级语言编写的程序转换为计算机能看懂的机器语言,这个过程就是编译的过程。理解了为什么需要编译过程,也就明白了编译的作用:编译过程就是把我们编写的源代码转换为机器可以识别的机器语言(二进制形式)。
理解了编译过程的作用,下面来详细看一下编译过程的具体细节。编译的过程可以分解为一下4个步骤:

预处理
编译
汇编
链接 
针对我这里面的三个文件main.c,head.h,fun.c,整体的编译过程就是这样的:

下面详细说一下每一步的内容
1、预处理:
预处理过程主要处理源代码文件中以“#”开始的预编译指令,比如“#include”,“define”等等。特别说一下“#incldue”指令,它会把被包含的文件插入到该预编译指令的位置,而且这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。在gcc下,利用-E选项可以生成预处理后的文件。于是利用下面两条命令:
gcc -E main.c -o main.i
gcc -E fun.c -o fun.i
生成预处理后的main.i文件和fun.i文件。你如果打开了main.i你会发现,单单是“#include<stdio.h>”一条,就会在main.c中插入几百行代码。
fun.i:


2、编译。编译过程是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件。这个过程往往是我们所说的整个程序构建的核心部分。我们可以用下面两条命令
gcc -S main.i -o main.s
gcc -S fun.i -o fun.s
产生编译的结果文件main.s 和 fun.s。这两个文件的内容现在完整的贴在下面,后面讲到运行的时候会更详细的分析他们。
main.s:
.file   "main.c"
2     .section    .rodata
3 .LC0:
4     .string "%d\n"
5     .text
6     .globl  main
7     .type   main, @function
8 main:
9 .LFB0:
10     .cfi_startproc
11     pushl   %ebp
12     .cfi_def_cfa_offset 8
13     .cfi_offset 5, -8
14     movl    %esp, %ebp
15     .cfi_def_cfa_register 5
16     andl    $-16, %esp
17     subl    $32, %esp
18     movl    $12, 20(%esp)
19     movl    $23, 24(%esp)
20     movl    24(%esp), %eax
21     movl    %eax, 4(%esp)
22     movl    20(%esp), %eax
23     movl    %eax, (%esp)
24     call    add
25     movl    %eax, 28(%esp)
26     movl    28(%esp), %eax
27     movl    %eax, 4(%esp)
28     movl    $.LC0, (%esp)
29     call    printf
30     movl    $0, %eax
31     leave
32     .cfi_restore 5
33     .cfi_def_cfa 4, 4
34     ret
35     .cfi_endproc
36 .LFE0:
37     .size   main, .-main
38     .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
39     .section    .note.GNU-stack,"",@progbits
fun.s:
1     .file   "fun.c"
2     .text
3     .globl  add
4     .type   add, @function
5 add:
6 .LFB0:
7     .cfi_startproc
8     pushl   %ebp
9     .cfi_def_cfa_offset 8
10     .cfi_offset 5, -8
11     movl    %esp, %ebp
12     .cfi_def_cfa_register 5
13     movl    12(%ebp), %eax
14     movl    8(%ebp), %edx
15     addl    %edx, %eax
16     popl    %ebp
17     .cfi_restore 5
18     .cfi_def_cfa 4, 4
19     ret
20     .cfi_endproc
21 .LFE0:
22     .size   add, .-add
23     .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
24     .section    .note.GNU-stack,"",@progbits


3、汇编。汇编过程就是利用汇编器,将汇编代码转变成机器可执行的指令,每一条汇编语句几乎都对应一条机器指令。我们用下面两条命令
gcc -c main.s -o main.o
gcc -c fun.s -o fun.o
生成包含机器指令的文件。需要注意的是,汇编的结果就是机器指令了,不同于我们前两步可以用不同文本编辑器能够看到内容的普通文件,它们是二进制格式的文件,在Linux下,是一种ELF格式的文件。
4、链接。链接简单的理解就是将多个目标文件合并生成一个最终的可执行文件。在Linux下,这个最终的可执行文件也是一个ELF格式。命令如下
gcc main.o fun.o -o res
这个res就是我们最终生成的可执行文件。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: