深入浅出iOS编译

本文深入探讨了iOS应用的编译过程,包括预处理、词法分析、语法分析、编译器前端Clang、后端LLVM、链接器的工作,以及XCode编译流程、头文件管理和签名机制。从源代码到机器码,再到最终的可执行文件,详细阐述了每个阶段的关键步骤和工具,旨在帮助开发者理解iOS应用编译的全貌。
摘要由CSDN通过智能技术生成

前言

两年前曾经写过一篇关于编译的文章《iOS编译过程的原理和应用》,这篇文章介绍了iOS编译相关基础知识和简单应用,但也很有多问题都没有解释清楚:

  • Clang和LLVM究竟是什么
  • 源文件到机器码的细节
  • Linker做了哪些工作
  • 编译顺序如何确定
  • 头文件是什么?XCode是如何找到头文件的?
  • Clang Module
  • 签名是什么?为什么要签名

为了搞清楚这些问题,我们来挖掘下XCode编译iOS应用的细节。

编译器

把一种编程语言(原始语言)转换为另一种编程语言(目标语言)的程序叫做编译器

大多数编译器由两部分组成:前端和后端。

  • 前端负责词法分析,语法分析,生成中间代码;
  • 后端以中间代码作为输入,进行行架构无关的代码优化,接着针对不同架构生成不同的机器码。

前后端依赖统一格式的中间代码(IR),使得前后端可以独立的变化。新增一门语言只需要修改前端,而新增一个CPU架构只需要修改后端即可。

Objective C/C/C++使用的编译器前端是clang,swift是swift,后端都是LLVM

LLVM

LLVM(Low Level Virtual Machine)是一个强大的编译器开发工具套件,听起来像是虚拟机,但实际上LLVM和传统意义的虚拟机关系不大,只不过项目最初的名字是LLVM罢了。

LLVM的核心库提供了现代化的source-target-independent优化器和支持诸多流行CPU架构的代码生成器,这些核心代码是围绕着LLVM IR(中间代码)建立的。

基于LLVM,又衍生出了一些强大的子项目,其中iOS开发者耳熟能详的是:ClangLLDB

clang

clang是C语言家族的编译器前端,诞生之初是为了替代GCC,提供更快的编译速度。一张图了解clang编译的大致流程:

接下来,从代码层面看一下具体的转化过程,新建一个main.c:

#include <stdio.h>
// 一点注释
#define DEBUG 1
int main() {
#ifdef DEBUG
  printf("hello debug\n");
#else
  printf("hello world\n");
#endif
  return 0;
}

预处理(preprocessor)

预处理会替进行头文件引入,宏替换,注释处理,条件编译(#ifdef)等操作

#include "stdio.h"就是告诉预处理器将这一行替换成头文件stdio.h中的内容,这个过程是递归的:因为stdio.h也有可能包含其头文件。

用clang查看预处理的结果:

xcrun clang -E main.c

预处理后的文件有400多行,在文件的末尾,可以找到main函数

int main() {
  printf("hello debug\n");
  return 0;
}

可以看到,在预处理的时候,注释被删除,条件编译被处理。

词法分析(lexical anaysis)

词法分析器读入源文件的字符流,将他们组织称有意义的词素(lexeme)序列,对于每个词素,此法分析器产生词法单元(token)作为输出。

$ xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.c

输出:

annot_module_include '#include <s'		Loc=<main.c:1:1>
int 'int'	 [StartOfLine]	Loc=<main.c:4:1>
identifier 'main'	 [LeadingSpace]	Loc=<main.c:4:5>
....

Loc=<main.c:1:1>标示这个token位于源文件main.c的第1行,从第1个字符开始。保存token在源文件中的位置是方便后续clang分析的时候能够找到出错的原始位置。

语法分析(semantic analysis)

词法分析的Token流会被解析成一颗抽象语法树(abstract syntax tree - AST)。

$ xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -f

main函数AST的结构如下:

[0;34m`-[0m[0;1;32mFunctionDecl[0m[0;33m 0x7fcc188dc700[0m <[0;33mmain.c:4:1[0m, [0;33mline:11:1[0m> [0;33mline:4:5[0m[0;1;36m main[0m [0;32m'int ()'[0m
[0;34m  `-[0m[0;1;35mCompoundStmt[0m[0;33m 0x7fcc188dc918[0m <[0;33mcol:12[0m, [0;33mline:11:1[0m>
[0;34m    |-[0m[0;1;35mCallExpr[0m[0;33m 0x7fcc188dc880[0m <[0;33mline:6:3[0m, [0;33mcol:25[0m> [0;32m'int'[0m[0;36m[0m[0;36m[0m
[0;34m    | |-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc868[0m <[0;33mcol:3[0m> [0;32m'int (*)(const char *, ...)'[0m[0;36m[0m[0;36m[0m <[0;31mFunctionToPointerDecay[0m>
[0;34m    | | `-[0m[0;1;35mDeclRefExpr[0m[0;33m 0x7fcc188dc7a0[0m <[0;33mcol:3[0m> [0;32m'int (const char *, ...)'[0m[0;36m[0m[0;36m[0m [0;1;32mFunction[0m[0;33m 0x7fcc188c5160[0m[0;1;36m 'printf'[0m [0;32m'int (const char *, ...)'[0m
[0;34m    | `-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc8c8[0m <[0;33mcol:10[0m> [0;32m'const char *'[0m[0;36m[0m[0;36m[0m <[0;31mBitCast[0m>
[0;34m    |   `-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc8b0[0m <[0;33mcol:10[0m> [0;32m'char *'[0m[0;36m[0m[0;36m[0m <[0;31mArrayToPoin
  • 27
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值