iOS底层探索(二) - Clang编译过程原理

2024-06-15 12:48

本文主要是介绍iOS底层探索(二) - Clang编译过程原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在前面

编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的。但要想提升技术却始终绕不开要对底层原理的探究,很多资料都是直接抛出一堆函数概念和一顿操作,基础一般的小伙伴看了表示一脸懵逼。在此结合我自己的理解进行优化总结一下。毕竟知识水平有限,有问题或总结不妥的地方欢迎指出,多多学习,非常感谢!2018.2

入门起步

  • 经过上一篇对编译器的基本介绍,相信大家对Clang都有一个基本的认识了,通俗来说是一个编译器的前端,负责分析源代码(就是我们使用的C/OC/C++等)。

Clang的编译过程

1.预处理

  • 预处理顾名思义是预先处理,那预处理都做了哪些事情呢?内容如下。

  • (1) import 头文件替换

    • 面向对象编程的思维下,我们写代码会经常用到其他类的属性\方法等,我们只需要导入头文件就可以用了,如:

      #import <Foundation/Foundation.h> 
      // 这里将会在预处理时会把 Foundation.h 文件的内容拷贝过来并替换
      
    • 基于这个原理,这里引出了一个小问题,如果 ClassA.h文件引用了 ClassB.h,并且 ClassB.h也引用了 ClassA.h,这里是不是就会互相循环引入了?

      • 解决办法是在头文件中使用
      @class ClassA;
      
      • 代替
      #import "ClassA.h"
      
      • 这么写意思是声明 ClassA是一个类,这样你就可以使用ClassA做类名了,如果需要使用 ClassA的方法属性等可以在 .m 实现文件中再通过 import MyClass.h的方式使用,这种方法不但可以解决互相引入的问题还可以优化编译速度。
  • (2) macro 宏展开

    • 无参宏: 如:

      #define DATA_TYPE_NUM @"number"
      

      在此宏定义作用域内,输入了 DATA_TYPE_NUM,在预处理过程中 DATA_TYPE_NUM 都会被替换成 @"number"。

    • 带参宏: 带参数的宏 如:

      #define CYXColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]
      
  • (3) 处理其他的预编译指令(其实预编译过程也是出了预编译指令的过程)

    条件编译语句也是在预处理阶段完成,并且条件编译只允许编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率,如以下代码就只会保留一个return语句:

    #if DEBUG        return YES;
    #elsereturn NO;
    #endif
    
  • (4) 总结:

    • 简单来说,“#”这个符号是编译器预处理的标志, 以下是一些常用的预处理指令参考
    预处理指令用法解析
    #undef取消已定义的宏
    #if如果给定条件为真,则编译以下代码
    #ifdef如果宏已经定义,则编译以下代码
    #ifndef如果宏没有定义,则编译以下代码
    #elif如果前面的#if给定条件不为真,当前条件为真,则编译以下代码
    #endif结束一个#if……#else条件编译块
*PS:还需要了解更多关于预编译的内容,还请自行百度*
[图片上传失败...(image-cf6f6f-1531632712782)][图片上传失败...(image-fd9112-1531632712782)]`$clang -E main.m`

2. Lexical Analysis - 词法分析(输出token流)

  • 预处理完成了以后,开始词法分析。词法分析其实是编译器开始工作真正意义上的第一个步骤,其所做的工作主要为将输入的代码转换为一系列符合特定语言的词法单元,这些词法单元类型包括了关键字,操作符,变量等等。举个例子:

Objective-C语言包含了关键字if、else、new等,那么在词法分析步骤时,遇到i与f或n与e与w组合在一起的时候,需要将这几个字母组合为关键字if或new这个词法单元。

  • 词法分析,只需要将源代码以字符文本的形式转化成Token流的形式,不涉及交验语义,不需要递归,是线性的。

    什么是token流呢?可以这么理解:就是有"类型",有"值"的一些小单元。

  • 再举个例子:

    比如一个运算表达式:(28 + 78) * 2这里面只需要解析出(是一个开括号,28是数字整形,+是一个运算符号即可。

编译指令: $clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

Snip20171231_7.png

 

Snip20171231_4.png

3.Semantic Analysis - 语法分析(输出(AST)抽象语法树)

编译指令:$clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

Snip20180122_2.png

 

  • 语法分析的最终产物是输出抽象语法树
  • 语法分析,在Clang中由Parser和Sema两个模块配合完成
  • 交验语法是否正确
  • 根据当前语言的语法,生成语意节点,并将所有节点组合成抽象语法树(AST)
  • 这一步跟源码等价,可以反写出源码
  • Static Analysis 静态分析
    • 通过语法树进行代码静态分析,找出非语法性错误
    • 模拟代码执行路径,分析出control-flow graph(CFG) 【MRC时代会分析出引用计数的错误】
    • 预置了常用Checker(检查器)

未完待续 ...

这是上篇,为保证博客质量与阅读体验(个人感觉一次阅读过多文字有点影响阅读体验),先分享已完成的上半部分,下篇将继续介绍Clang编译过程中的剩下环节,欢迎持续关注,感谢理解与支持!2018.2

预告:下篇将继续介绍Clang与LLVM以下环节的相关知识。

下面是一些关键词,有兴趣的朋友先自行谷歌学习吧,下篇等我有闲情的时候再更新了,我也不知道什么时候。2018.7.15

4. CodeGen - (Intermediate Representation,简称IR)IR中间代码生成

  • CodeGen 负责将语法树丛顶至下遍历,翻译成LLVM IR
  • LLVM IR 是Frontend的输出,也是LLVM Backend的输入,前后端的桥接语言 (Swift也是转成这个)
  • 与 Objective-C Runtime 桥接
    • Class/Meta Class/Protocol/Category内存结构生成,并存放在指定section中(如Class:_DATA, _objc_classrefs)
    • Method/lvar/Property内存结构生成
    • 组成method_list/ivar_list/property_list并填入Class
    • Non-Fragile ABI:为每个Ivar合成OBJC_IVAR_$_偏移值常量
    • 存取Ivar的语句(ivar = 123; int a = ivar;)转写成base + OBJC_IVAR$_的形式
    • 将语法树中的ObjcMessageExpr翻译成相应版本的objc_msgSend,对super关键字的调用翻译成objc_msgSendSuper
    • 根据修饰符strong/weak/copy/atomic合成@property 自动实现的 setter/getter
    • 处理@synthesize
    • 生成block_layout的数据结构
    • 变量的capture(__block/__weak)
    • 生成_block_invoke函数
    • ARC:分析对象引用关系,将objc_storeStrong/objc_storeWeak等ARC代码插入
    • 将ObjCAutoreleasePoolStmt转译成objc_autoreleasePoolPush/Pop
    • 实现自动调用[super dealloc]
    • 为每个拥有ivar的Class合成.cxx_destructor方法来自动释放类的成员变量,代替MRC时代的“self.xxx = nil”

5. Optimize - 优化IR

  • 递归优化成尾递归

6. LLVM Bitcode - 生成字节码

7. Assemble - 生成Target相关汇编

  • Assemble - 生成Target相关Object(Mach-O)

8. Link生成Executable

 

这篇关于iOS底层探索(二) - Clang编译过程原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063498

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu

hdu4059容斥原理

求1-n中与n互质的数的4次方之和 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWrit