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

举报
云物互联 发表于 2021/08/05 23:58:54 2021/08/05
【摘要】 目录 文章目录 目录文章目录C 程序的编译流程预处理编译汇编链接编译多个源文件 文章目录 《C 语言编程 — GCC 工具链》 《C 语言编程 — 程序的编译流程》 《C 语言编程 — 静态库、动态库和共享库》 《C 语言编程 — 程序的装载与运行》 《计算机组成原理 — 指令系统》 《C 语言编程 — 结构化程序流的汇编代码与 CPU 指令集》 ...

目录

文章目录

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;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

预处理

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

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

  
 
  • 1
  • -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;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

编译

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

  • 词法分析:解析代码的 Tokens。
  • 语法分析:建立抽象语法树。
  • 语义分析:编译器通过语法树来理解代码的含义。检测出非法的语义。
gcc -S -I . hello.i -o hello.s

  
 
  • 1
  • -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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

汇编

汇编过程对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为 .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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

链接

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

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

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

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

gcc hello.c -o hello

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

  
 
  • 1

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

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

编译多个源文件

  • main.c
#include "hello.h"

int main(void)
{ print("hello world"); return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • hello.c
#include "hello.h"

void print(const char *str)
{ printf("%s\n", str);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • hello.h
#ifndef _HELLO_H
#define _HELLO_H

#include <stdio.h>

void print(const char *str);

#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

一次性编译

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

$ ./main 

  
 
  • 1
  • 2
  • 3

独立编译

$ 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 

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。

原文链接:is-cloud.blog.csdn.net/article/details/106912436

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。