C 语言编程 — 程序的编译流程

目录

文章目录

C 语言编程 — GCC 工具链
C 语言编程 — 程序的编译流程
C 语言编程 — 静态库、动态库和共享库
C 语言编程 — 程序的装载与运行
计算机组成原理 — 指令系统
C 语言编程 — 结构化程序流的汇编代码与 CPU 指令集

C 程序的编译流程

在这里插入图片描述

虽然我们称 GCC 是 C 语言的编译器,但由 C 语言源代码文件到生成可执行文件的过程不仅仅是编译的过程,而是要经历以下四个相互关联的步骤:

  1. 预处理(Preprocessing)
  2. 编译(Compilation)
  3. 汇编(Assembly)
  4. 链接(Linking)

在这里插入图片描述

示例代码

#include<stdio.h>

int main(void)
{
    printf("Hello World!\n");
    return 0;
}

预处理

预处理(Preprocessing):GCC 首先调用预处理程序 cpp 进行预处理,在预处理过程中,.c 文件中的文件包含(include)、预处理语句(e.g. 宏定义 define 等)进行分析,并替换成为真正的内容。

  1. 执行所有的预处理器指令,并且展开所有的宏定义。
  2. 删除所有注释。
  3. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
  4. 保留所有的 #pragma 预处理器指令,后续编译过程需要使用它们。
gcc -E -I . hello.c -o hello.i
  • -E 是让编译器在预处理之后就退出
  • -I 指定头文件目录
  • -o 指定输出文件名

下面可以看到预处理后,代码从 7 行扩展到了 845 行。.i 文件里面包含了所有 include 和宏定义的真正内容。

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

# 2 "hello.c" 2

# 3 "hello.c"
int
main(void)
{
  printf("Hello World!" "\n");
  return 0;
}

编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

gcc -S -I . hello.i -o hello.s
  • -S 让编译器在编译之后停止

下面可以看到编译后的汇编代码:

main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

汇编

汇编过程对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为 .o 的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编过程相对于编译过程比较简单。通过调用 Binutils 工具中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可。一般来讲,.S 文件和 .s 文件经过预处理和汇编之后都会生成以 .o 的目标文件。

当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成 .o 目标文件后,才能进入下一步的链接工作。

注意:目标文件已经是最终程序的某一部分了,但是在链接之前还不能执行。
注意:hello.o 目标文件为 ELF(Executable and Linkable Format)格式的可重定向文件。

$ gcc -c hello.s -o hello.o
# or
$ as hello.s -o hello.o

$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接

链接也分为静态链接和动态链接:

  • 静态链接:是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。

  • 动态链接:则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

  • 动态链接:用动态库进行链接。

gcc hello.c -o hello
  • 静态链接:用静态库进行链接。
gcc -static hello.c -o hello

可以使用 Binutils 的 size 命令查看生成的 ELF 可执行文件的大小,可以使用 Binutils 的 ldd 命令查看链接的动态库。

链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常被链接为不同的段,常见的段譬如 .text、.data、.rodata、.bss 等。

编译多个源文件

  • main.c
#include "hello.h"

int main(void)
{
    print("hello world");
    return 0;
}
  • hello.c
#include "hello.h"

void print(const char *str)
{
    printf("%s\n", str);
}
  • hello.h
#ifndef _HELLO_H
#define _HELLO_H

#include <stdio.h>

void print(const char *str);

#endif

一次性编译

$ gcc -Wall hello.c main.c -o main

$ ./main 

独立编译

$ gcc -Wall -c main.c -o main.o
$ gcc -Wall -c hello.c -o hello.o
$ gcc -Wall hello.o main.o -o newmain

$ ./newmain 

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/106912436