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, -32sw      ra, 28(sp)                      # 4-byte Folded Spillsw      s0, 24(sp)                      # 4-byte Folded Spilladdi    s0, sp, 32addi    a0, zero, 1sw      a0, -12(s0)addi    a0, zero, 2sw      a0, -16(s0)lw      a0, -12(s0)lw      a1, -16(s0)mul     a0, a0, a1sw      a0, -20(s0)mv      a0, zerolw      s0, 24(sp)                      # 4-byte Folded Reloadlw      ra, 28(sp)                      # 4-byte Folded Reloadaddi    sp, sp, 32ret
.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又实现了一遍。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/596318.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地Windows打包启动前端后台

本地Windows打包启动前端后台 1、安装jdk Windows JDK安装 2、Nginx 2.1、将 nginx-1.16.1文件夹复制到D:\home\jisapp目录下 2.2、域名证书配置&#xff1a; 将域名证书放到D:\home\jisapp\ssl\2023目录下->配置nginx.conf文件&#xff08;D:\home\jisapp\nginx-1.22.0…

智能感应门改造工程

今天记录一下物联网专业学的工程步骤及实施过程 智能感应门改造工程 1 规划设计1.1 项目设备清单1.2项目接线图 软件设计信号流 设备安装与调试工程函数 验收 1 规划设计 1.1 项目设备清单 1.2项目接线图 软件设计 信号流 设备安装与调试 工程函数 工程界面: using System; …

新手如何开始运营朋友圈?分享朋友圈前5条内容运营技巧!

最近加入的新伙伴比较多&#xff0c;不少伙伴反馈一个问题&#xff1a;作为新人&#xff0c;前期我们的朋友圈要如何发&#xff1f;要怎么开始发朋友&#xff1f;要怎么配图&#xff0c;怎么配文案&#xff1f; 为了解决新伙伴们的这个问题&#xff0c;今天同伙伴们分享&#…

7.二叉树的遍历方式及二叉树习题

4.二叉树链式结构的实现 二叉树是&#xff1a; 空树 非空&#xff1a;根节点&#xff0c;根节点的左子树、根节点的右子树组成的。 4.1二叉树的遍历 4.2.1 前序、中序以及后序遍历 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前…

华清远见STM32MP157开发板助力嵌入式大赛ST赛道MPU应用方向项目开发

第七届&#xff08;2024&#xff09;全国大学生嵌入式芯片与系统设计竞赛&#xff08;以下简称“大赛”&#xff09;已经拉开帷幕&#xff0c;大赛的报名热潮正席卷而来。嵌入式大赛截止今年已连续举办了七届&#xff0c;为教育部认可的全国普通高校大学生国家级A类赛事&#x…

数据结构和算法:分治

分治算法 分治&#xff08;divide and conquer&#xff09;&#xff0c;全称分而治之&#xff0c;是一种非常重要且常见的算法策略。分治通常基于递归实现&#xff0c;包括“分”和“治”两个步骤。 1.分&#xff08;划分阶段&#xff09;&#xff1a;递归地将原问题分解为两个…

Spring源码解析-容器基本实现

spring源码解析 整体架构 defaultListableBeanFactory xmlBeanDefinitionReader 创建XmlBeanFactory 对资源文件进行加载–Resource 利用LoadBeandefinitions(resource)方法加载配置中的bean loadBeandefinitions加载步骤 doLoadBeanDefinition xml配置模式 validationMode 获…

Open CASCADE学习|放样建模

在CAD软件中&#xff0c;Loft&#xff08;放样&#xff09;功能则是用于创建三维实体或曲面的重要工具。通过选取两个或多个横截面&#xff0c;并沿这些横截面进行放样&#xff0c;可以生成复杂的三维模型。在CAD放样功能的操作中&#xff0c;用户可以选择不同的选项来定制放样…

二分答案 蓝桥杯 2022 省A 青蛙过河

有些地方需要解释&#xff1a; 1.从学校到家和从家到学校&#xff0c;跳跃都是一样的&#xff0c;直接看作2*x次过河就可以。 2.对于一个跳跃能力 y&#xff0c;青蛙能跳过河 2x 次&#xff0c;当且仅当对于每个长度为 y 的区间&#xff0c;这个区间内 h 的和都大于等于…

并发编程01-深入理解Java并发/线程等待/通知机制

为什么我们要学习并发编程&#xff1f; 最直白的原因&#xff0c;因为面试需要&#xff0c;我们来看看美团和阿里对 Java 岗位的 JD&#xff1a; 从上面两大互联网公司的招聘需求可以看到&#xff0c; 大厂的 Java 岗的并发编程能力属于标配。 而在非大厂的公司&#xff0c; 并…

安卓主板MT8390(Genio 700)_MTK联发科Linux开发板方案

MediaTek Genio 700 &#xff08;MT8390&#xff09;是一款高性能的边缘 AI 物联网平台&#xff0c;专为智能家居、互动零售、工业与商业应用而设计。提供快速响应的边缘计算能力、先进的多媒体功能、广泛的传感器和连接方式&#xff0c;且支持多任务操作系统。 MT8390安卓核心…

C# 委托的基础应用

一、Action 和 Func 的使用。 二、自定义委托&#xff1a; 完整的使用代码示例&#xff1a; 三、委托的一般使用 模板方法&#xff1a; 回调方法&#xff0c;在模板方法的基础上进行添加。