程序是怎么诞生的,从hello-world说起

本文从一个简单的例子来说明我们写的代码是怎么办成可执行程序的


#include 

int main()
{
        printf("hello world\n");
        return 0;
}

以上是一个最简单的c程序源码

这个源码是怎么办成可执行程序呢

平时使用IDE开发过程中我们只需要写好源码文件,点击运行或按下F5即可,但这看似简单的操作背后隐藏了一系列不可或缺的过程。

一个c程序从源码到可执行程序需要经过 预处理 编译 汇编 链接 这几个阶段

预处理阶段

预处理器会分析源程序,解析其中的预处理指令(#开头的指令)
使用gcc -E参数[1]可以查看预处理后的结果

  -E Preprocess only; do not compile, assemble or link.

执行gcc -E hello.c > hello.i我们发现输出的文件中上部分是stdio.h头文件内容,下面是我们的源码文件

...省略...

extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;


extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 868 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2


# 3 "hello.c"
int main(){
 printf("hello world\n");
 return 0;
}

编译阶段

编译器会把上阶段展开后的源码文件翻译成汇编文件
使用gcc -S参数可以查看编译后的结果

  -S Compile only; do not assemble or link.

执行 gcc -S hello.i -o hello.s将会生成hello.s汇编语言文件

  1     .file   "hello.c"
  2     .text
  3     .section    .rodata
  4 .LC0:
  5     .string "hello world"
  6     .text
  7     .globl  main
  8     .type   main, @function
  9 main:
 10 .LFB0:
 11     .cfi_startproc
 12     pushq   %rbp
 13     .cfi_def_cfa_offset 16
 14     .cfi_offset 6, -16
 15     movq    %rsp, %rbp
 16     .cfi_def_cfa_register 6
 17     leaq    .LC0(%rip), %rdi
 18     call    puts@PLT
 19     movl    $0, %eax
 20     popq    %rbp
 21     .cfi_def_cfa 7, 8
 22     ret
 23     .cfi_endproc
 24 .LFE0:
 25     .size   main, .-main
 26     .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
 27     .section    .note.GNU-stack,"",@progbits

汇编阶段

将汇编语言文件生成relocatable的目标文件
通过gcc -c参数可以实现编译,汇编不链接

  -c Compile and assemble, but do not link.

我们执行gcc -c hello.c后生成hello.o文件
通过file hello.o命令可以查看该文件格式为
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接阶段

最后由链接器将生成的.o文件和其它引用的.o文件一起链接成一个可执行程序 hello


  1. gcc预处理参数 ↩

你可能感兴趣的:(程序是怎么诞生的,从hello-world说起)