C语言程序编译全流程,从源代码到二进制

源程序

对于一个最简单的程序:

int main(){
	int a = 1;
	int b = 2;
	int c = a + b;
	return 0;
}

预处理

处理源代码中的宏指令,例如#include等

clang -E test.c

处理结果:

# 1 "test.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 343 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2
int main(){
    int a=1;
    int b=2;
    int c=a+b;
    return 0;
}

预处理输出中多出来的这些行,被称为linemarkers,
格式为:# linenum filename flags
具体意思可以看gnu的文档以及Cppreference的文档

RTFM:reading the f**k manual

编译过程

词法分析

clang -fsyntax-only -Xclang -dump-tokens test.c
# Clang编译器的一个选项,它告诉编译器只进行语法分析而不生成目标代码。这意味着编译器将检查源文件中的语法错误,但不会生成可执行文件或目标文件。
#-Xclang: 这是Clang编译器的一个选项,它允许传递额外的选项给Clang编译器的底层部分。
#-dump-tokens 告诉编译器在对源代码进行词法分析后输出标记(tokens)的信息。

生成结果:
在这里插入图片描述
词法分析把原文件划分为一个个的token,记录下每个token的类型,内容,位置(所在文件,行号列号)
类型有:

  • 关键字(Keywords):例如int、for、if等。
  • 标识符(Identifiers):由用户定义的名称,用于表示变量、函数等。
  • 常量(Constants):包括整数常量、浮点数常量、字符常量、字符串常量等。
  • 字符(Characters):例如单引号括起来的字符常量。
  • 字符串(Strings):例如双引号括起来的字符串常量。
  • 运算符(Operators):例如+、-、*等。
  • 分隔符(Delimiters):例如逗号,、分号;、括号(、)等。
  • 注释(Comments):包括单行注释//和多行注释/* */。
  • 空白符(Whitespace):例如空格、制表符、换行符等。
    这一步一般不会报错

语法分析

将函数

clang -fsyntax-only -Xclang -ast-dump test.c
# -ast-dump: 这个选项告诉Clang编译器在语法分析之后输出抽象语法树(Abstract Syntax Tree,AST)。抽象语法树是编译器在语法分析阶段生成的一种树形结构,它反映了源代码的语法结构,是编译器进行进一步分析和优化的基础

输出【clang的语法树输出把语义信息也输出了】
在这里插入图片描述
这一步将识别出来的结果组合成树型结构,称为语法树,包括每个节点的类型,节点间的关系等。
上面的TranslationUnitDecl就是指一个翻译单元,一般一个源文件就是一个翻译单元,上面的一些invalid sloc的是一些C语言内置的东西,不用看。
从FuncitonDecl开始,是我们写的代码的语法树
VarDecl指变量声明。
BinaryOperator是二进制操作符,也就是+号,
LValueToRValue是指C语言中的左值和右值机制,a和b是左值,先转化为右值,然后再相加把结果赋给c。
在这一步会报告一些语法错误,例如缺了分号。
关于Clang的AST的详细信息可以看官方文档

语义分析

按照C语言的语义确定AST中每个表达式的类型,相容的类型将根据C语言标准规范进行类型转换,例如两个不同类型的数相加这种,会进行隐式转换。
报告一些语义错误,例如:未定义的引用,运算符的操作数类型不匹配(如struct + int),函数调用与定义的参数数量不一致等。

静态程序分析

对代码进行静态分析,检查其中的语法错误, 代码风格和规范, 潜在的软件缺陷, 安全漏洞, 性能问题等

clang --analyze -Xanalyzer -analyzer-output=text ./test.c
#--analyze: 这是Clang编译器的一个选项,指示编译器执行静态代码分析。它告诉Clang不仅要编译代码,还要分析代码中潜在的问题。
#-Xanalyzer: 这个选项允许将后续参数传递给分析器。
#-analyzer-output=text: 这个选项告诉分析器以文本形式输出分析结果。换句话说,它指示分析器将结果以易读的文本形式打印到终端。

输出:
在这里插入图片描述

中间代码生成

生成IR中间代码。
IR:编译器定义的, 面向编译场景的指令集,与源代码编程语言和后端运行平台架构都无关得指令集。
Clang使用的是LLVM IR,gcc使用的是GIMPLE。
在这里插入图片描述

优化

在确保代码可观测行为一致的情况下,对代码进行优化,如果把程序看作状态机,那么优化就是指使用尽可能简单的状态机来代替复杂的状态机。
对volatile修饰变量的访问需要严格执行
程序结束时, 写入文件的数据需要与严格执行时一致
交互式设备的输入输出(stdio.h)需要与严格执行时一致
Clang or Gcc中的优化等级都是可控的,优化等级通过-O选项指定,其取值范围为0到3,其中0表示不进行优化,1表示基本优化,2表示更多的优化,3表示更加激进的优化。

clang -O1 ./test.c
#-O1表示基本优化
clang -S -foptimization-record-file=- a.c -O1
#-foptimization-record-file选项表示输出优化记录,-表示直接输出到终端,或者可以输入一个文件名,表示输出到文件中,格式为yaml

输出结果:

--- !Analysis
Pass:            prologepilog
Name:            StackSize
DebugLoc:        { File: test.c, Line: 1, Column: 0 }
Function:        main
Args:
  - NumStackBytes:   '0'
  - String:          ' stack bytes in function'
...
--- !Analysis
Pass:            asm-printer
Name:            InstructionMix
DebugLoc:        { File: test.c, Line: 6, Column: 1 }
Function:        main
Args:
  - String:          'BasicBlock: '
  - BasicBlock:      ''
  - String:          "\n"
  - String:          retq
  - String:          ': '
  - INST_retq:       '1'
  - String:          "\n"
  - String:          'xorl      '
  - String:          ': '
  - INST_xorl:       '1'
  - String:          "\n"
...
--- !Analysis
Pass:            asm-printer
Name:            InstructionCount
DebugLoc:        { File: test.c, Line: 1, Column: 0 }
Function:        main
Args:
  - NumInstructions: '2'
  - String:          ' instructions in function'
...

目标代码生成

clang -S test.c --target=riscv32-linux-gnu

生成的代码:

.text
        .attribute      4, 16
        .attribute      5, "rv32i2p0_m2p0_a2p0_f2p0_d2p0_c2p0"
        .file   "test.c"
        .globl  main                            # -- Begin function main
        .p2align        1
        .type   main,@function
main:                                   # @main
# %bb.0:
        addi    sp, sp, -32
        sw      ra, 28(sp)                      # 4-byte Folded Spill
        sw      s0, 24(sp)                      # 4-byte Folded Spill
        addi    s0, sp, 32
        addi    a0, zero, 1
        sw      a0, -12(s0)
        addi    a0, zero, 2
        sw      a0, -16(s0)
        lw      a0, -12(s0)
        lw      a1, -16(s0)
        mul     a0, a0, a1
        sw      a0, -20(s0)
        mv      a0, zero
        lw      s0, 24(sp)                      # 4-byte Folded Reload
        lw      ra, 28(sp)                      # 4-byte Folded Reload
        addi    sp, sp, 32
        ret
.Lfunc_end0:
        .size   main, .Lfunc_end0-main
                                        # -- End function
        .ident  "clang version 13.0.0 (https://github.com/llvm/llvm-project/ 24c8eaec9467b2aaf70b0db33a4e4dd415139a50)"
        .section        ".note.GNU-stack","",@progbits
        .addrsig

还可以使用ftime-report来查看编译中都做了什么,每个流程都花了多少时间。

clang -S test.c --target=riscv32-linux-gnu -ftime-report

汇编

这一步已经和编译原理不怎么搭边了

clang -c test.s

输出文件为一个.o文件,这个文件是二进制的,无法用cat等工具来看,cat和vim这些工具都是需要有相应的给人看的编码方式,例如UTF-8等。.o文件是为了给计算机看的,使用的编码方式是ISA规定的,具体可以看ISA的手册,每一个汇编语句对应一段二进制代码。
但是我们可以用objdump来看:

llvm-objdump -d test.o
#GNU也有objdump工具,但是需要给出二进制所对应的硬件ISA,llvm的objdump会自动识别ISA

输出结果:
在这里插入图片描述
objdump的原理跟汇编器的原理正好反过来的,objdump根据二进制指令反猜汇编代码,得到上图所示。

相关工具

clang是基于LLVM作为后端的,clang本身只是一个前端,LLVM不止是一个工具,而是多个工具集合到一起的,clang本身只负责将C或C++翻译成LLVM IR,再往后由LLVM IR到目标代码并不是由clang完成的,而是clang调用LLVM相应的工具。
clang中主要包括:
编译要用到的:

  • clang,从程序员的角度看就是C/C++编译器
  • llc:将LLVM IR的代码转化为目标代码
  • llvm-as:将LLVM IR的“汇编”转化为LLVM的“bitcode”【二者是一个东西不同形式,都是LLVM IR】
  • llvm-link:将多个LLVM bitcode文件链接为一个bitcode文件
  • lld链接器,为了替代系统链接器,输入多个.o文件和.a文件,链接成可执行文件eg:ELF。
    • 目前Linux系统自带的链接器一般都是GNU开发的ld链接器,lld文档里号称比ld要快很多。

配套的工具:

  • static analyzer:静态代码分析
  • llvm-objdump:objdump是一个用于分析目标文件(包括可执行文件、共享库、目标文件等)的工具。它可以显示目标文件的各种信息,包括可执行代码的汇编指令、符号表、段信息等。objdump通常与GNU Binutils软件包一起提供,是开发和调试工具链中的一部分。通过objdump,开发人员可以深入了解目标文件的结构和内容,有助于调试和优化程序。
  • llvm-strace:系统调用跟踪
  • llvm-size:输出二进制文件的大小信息
  • llvm-nm:列出二进制文件中的符号名

配套的工具就那么几种,GNU Binutils里面已经都实现过一遍了,只不过LLVM又实现了一遍。
在这里插入图片描述

相关推荐

  1. C#编程语言入门深入学习大纲

    2024-04-07 02:24:03       39 阅读
  2. gitclonepr的流程

    2024-04-07 02:24:03       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-07 02:24:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-07 02:24:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-07 02:24:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-07 02:24:03       20 阅读

热门阅读

  1. set容器

    2024-04-07 02:24:03       21 阅读
  2. 力扣经典150题第三题:删除有序数组中的重复项

    2024-04-07 02:24:03       25 阅读
  3. yolov8训练流程

    2024-04-07 02:24:03       32 阅读
  4. 【物联网】Qinghub opc-da 连接协议

    2024-04-07 02:24:03       21 阅读
  5. 蓝桥杯算法基础(38)c++ STL

    2024-04-07 02:24:03       17 阅读
  6. UI python 中的basePage 类元素的最全的相关公共方法

    2024-04-07 02:24:03       36 阅读
  7. 数据库第四次作业

    2024-04-07 02:24:03       22 阅读