基础知识:篇1-单源文件编译过程

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:
内容来源
  CSDN-chablin-程序的编译、链接过程详解
  CSDN-douguailove-编译和链接的过程博客园-星星_xing-Linux中程序的编译和链接过程

下一篇:基础知识:篇2-多源文件编译过程

零、GCC编译工具链 简介

  以 GCC 编译器为核心的一整套工具,用于把源代码转化成可执行应用程序,主要包含以下三部分:
    (1)GCC 编译器 : 用于完成预处理和编译过程,例如把 C 代码转换成汇编代码。
    (2) glibc:包含了主要的 C 语言标准函数库, C 语言中常常使用的打印函数 printf、 malloc 函数就在 glibc 库中。
    (2)Binutils :除GCC编译器外的一系列小工具包括了链接器ld,汇编器as、目标文件格式查看器readelf等。

一、编程过程

情景说明:

  ubuntu16.04-server下编写最简单的程序hello.c并利用gcc编译器编译后执行。

第一步:编写源文件

  输入指令vim hello.c【使用vim编辑器编辑hello.c(若无hello.c则创建)】

#include <stdio.h>

int main (int argc ,char **argv)
{
        printf("hello\n");
        return 0;
}

第二步:编译源文件

  输入指令gcc hello.c 【gcc编译器更准确说是驱动程序,指挥组件依次完成相应功能】
(组件:cpp(预编译器)【预编译过程】、cc1(编译器)【编译过程】、as【汇编过程】、ld【链接过程】等)
  输入指令ls,可看到文件a.out
  PS:
    默认编译结果均为:a.out,若想指定输出文件名则可使用,gcc选项:-o,即: gcc hello.c -o hello
    -o选项可用于任意阶段指定输出文件名,如:-o file ,指定文件名为file。
【常见后缀名与文件类型对应关系】

文件后缀名文件类型
.c 源文件
.h 头文件
.i 预处理文件
.s 汇编文件
.o 目标文件
a.out 可执行文件

第三步:运行可执行文件

  输入指令./a.out,可查看到输出结果:hello.


二、详细编译过程(第二步的详细说明)

  上面可知gcc是控制若干组件完成编译,那么具体是怎么样的呢?
在这里插入图片描述
【gcc选项】

总体选项 用途
-v选项 显示制作GCC工具自身时的配置命令;
同时显示编译器驱动程序、预处理器、编译器的版本号
-o选项 指定输出文件名,任意阶段均可使用。
-E选项 执行预处理后就停下来,直接送往标准输出。
-S选项 执行编译后就停下来,输出.s文件。
-c选项 执行汇编后就停下来,输出.o文件。
警告选项 用途
-Wall选项 打开所有需要注意的警告信息
调试选项 用途
-g选项 产生符号调试工具(GNU的gdb)所必要的符号资讯
优化选项 用途
-O0~-O3选项 -O0不优化,剩余优化等级随数字提高

  PS:
    -E、-S、-c选项相当于是限定了编译器执行操作结束点,而不是单独的将某一步拎出来执行(并不一定从头开始,从中间开始效果也相同)

①预编译过程

  工具:cpp工具
  目的:按预处理命令将源代码进行添加、替换、删除构成一份完整的源代码。
  (①删除所有的注释//和/* */。②添加行号和文件名标识。③保留所有的#progma编译器指令。④下面的预处理命令)
  预处理命令:以“#”开头的命令。
    包含:
      ①#include 包含命令(添加)
        目的:将一个源文件全部内容复制到当前源文件中
      ②#define 宏定义命令(替换)
        目的:提高程序通用性与易读性便于维护。
      ③#if/#ifdef等 条件编译命令(删除)
        目的:按照一定条件只编译源程序部分代码。
  输入指令 gcc -E hello.c -o hello.i / cpp hello.c > hello.i
  输入指令vim hello.i

。。。。。。
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 912 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main (int argc ,char **argv)
{
	printf("hello\n");
	return 0;
}

  该处简略截取其中一段代码,源程序只有寥寥数行,经过预编译后代码量变大。这是由于载入了stdio.h文件(位于/usr/include)中内容,不妨可打开查看一下。
  输入指令vim /usr/include/stdio.h
在这里插入图片描述
  可发现画圈几行在hello.i文件中可找到。

②编译过程

  工具:cc1工具
  目的:将上面的.i文件中的源代码“翻译”(进行一系列的词法分析、语法分析、语义分析及优化)为汇编代码。
  PS:
    翻译的汇编代码中包含伪指令(不参与CPU运算,只指导编译连接过程)
      如:.cfi开头伪指令,辅助汇编器创建栈帧(stack frame),每次函数调用过程均会产生栈帧
      (回溯(backtrace)查看函数调用信息可用到【又称栈的回卷(unwind stack)】)
  输入指令gcc -S hello.i -o hello.s gcc -S hello.c
  输入指令vim hello.s

        .file   "hello.c"
        .section        .rodata
.LC0:
        .string "hello"
        .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.12) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

③汇编过程

  工具:as工具
  目的:将上面的.s文件中的汇编代码“直译”为一定格式(可重定位)的机器代码(二进制文件)。
    (Linux系统上表现为ELF的目标文件[OBJ文件,Object file])
  PS:
    反汇编:机器代码转换为汇编代码(调试程序时经常用)
    简述汇编过程
      每一条汇编语句几乎都对应一条机器指令,故汇编过程非常简单,无复杂语法,语义,无需指令优化
      直接根据汇编指令和机器指令对照表翻译即可,除此之外,还需在目标文件中创建辅助链接所需的信息。
    可重定位:数据在内存中存放的起始位置不是固定的,起始位置+相对地址=绝对地址
    问题:为什么.o文件不能直接执行呢?
    回答:因为变量转成符号,符号表的符号地址没有分配出来,所以不能直接执行。
  输入指令gcc -c hello.s -o hello.o / as hello.s -o hello.ogcc -c hello.c
  输入指令vim hello.o
在这里插入图片描述

④链接过程

  工具:ld工具
  目的:将上面的.o文件和系统库中的.o文件与库文件链接起来形成可执行文件。
  PS:
    目标文件就是源代码经过编译后但未进行链接的那些中间文件(Linux下的 .o文件就是目标文件)
    目标文件和可执行文件内容和格式几乎都一样,都是按照ELF文件格式存储的
    简述链接过程
      将各个.o文件之间相互引用的部分正确的衔接起来。(就是把.o文件里的段整合在一起进行【符号解析】,解析正确后,再进行【符号重定位】,所有符号都拥有其正确的虚拟地址后,然后生成可执行文件,其运行的时候只需要代码段和数据段。)
    符号解析:将每个符号引用刚好和一个符号定义联系起来,对符号表里的未定义的符号找到其定义的地方,代码段中,对所有指令未有其定义的符号都将其变为正确的地址
    符号重定位:重新计算各个目标的地址过程叫做符号重定位(对其分配相应的虚拟地址)。
  输入指令gcc hello.o -o hello gcc hello.c -o hello

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值