开篇
举一个简单的的例子,老婆说了句“你给我滚出去睡沙发”。这句话的处理流程事这样的
程序的编译其实就是计算机在执行过程之前,把老婆的命令转换成电信号的过程。在计算机世界中,这个玩意儿叫过编译器(compiler),什么GCC啊clang啊什么什么的都是说的这个东西。看起来很高大上,其实就是一个翻译的东西。人把老婆的指令翻译成电波,编译器把各种语言翻译成01010101….
ps : 这篇文章是我在阅读了戴伟来的文章之后,根据原文整理的一个笔记。所以文中会有很多地方都摘录了文中的句子。感谢@戴伟来。如果你想直接看一下原文的话,最后有链接。
编译器的工作原理基本上都是三段式的,前端、优化器、后端。
前端负责解析源码,检查语法错误,并将其翻译成抽象的语法树;
优化器对这一中间代码进行优化,使代码更佳高效。
后端负责将优化过的代码转化成对应机器的代码。
LLVC编译器实际上是用C++写的,那C++的编译器呢?当然是汇编了,所以编译器和计算机语言的进步就是这样迭代发展的,再之后就是用高级语言写更高级的编译器,更高级的编译器就能编译更加高级的语言….那么问题来了,世界上计算机语言那么多,各种不同的架构,Intel ARM,怎么让编译语言分别产生不同的架构的执行码呢,这个时候就应该想到刚刚的三段式模型了,当我们要支持多种语言的时候,只需要添加多个前端就可以了。当我们要支持多种目标机器的时候,只需要添加多个后端就可以了。对于中间的优化器,我们可以使用通用的中间代码。gcc可以支持c、cpp、java….等语言的编译。
正文
由于文中作者使用的是Xcode6,但是现在我已经用到了Xcode7.3,而Xcode8页已经beta了。所以我讲使用我目前正在使用的Xcode7.3来进行本文。由于APPLE已经不在Xcode中内置GCC了,所以我就只能研究研究LLVM了。
如果对各种编译器的区别感兴趣的话可以看看下面两篇文章
如果阅读过优秀的源码,你一定会看到很多的#define
#if
#error
之类的代码,预编译对程序之后的编译提供了很多方便以及优化,对于错误处理,包引用、跨平台等都有着极大的帮助。
包含文件
#include
#include" "
、#include< >
的区别:#include“”
包含和使用#include < >
包含的不同之处就是使用<>包含时,预处理器会搜索C函数库头文件路径下的文件,而使用“”包含时首先搜索程序所在目录,其次搜索系统Path定义目录,如果还是找不到才会搜索C函数库头文件所在目录。
使用#include的时候包含文件的时候是不能递归包含的,例如a.h文件包含b.h,而b.h就不
能再包含a.h了;还有就是重复包含(比如a.h包含了b.h,然后main.c中又包含了a.h和b.h)虽然是允许的但是这会降低编译性能。那该怎么办呢?
- 使用#import替代include
- 使用宏判断(宏判断下面会详解),xcode很聪明,只要新建一个头文件a.h 里面就自动就生成了
#include_next
这个是非C标准库里面的预处理指令,我没有用过。
#import
OC特有的,智能的#include
,解决了#include
的重复包含问题。
宏定义
#define
|
|
#define
关键字表面即将开始定义一个宏,M_PI是宏的名字,空格过后是宏的内容。类似这样的宏编译器会在语义分析之后讲M_PI替换为3.14159,这是宏的最基本用法。
还有一种是函数宏函数宏顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如我在开发中最常用的两个宏
|
|
这两个宏的意思应该不用我说了吧,如果你没看懂而且你又喜欢用block的话,那建议你用instrument 的leaks看看是不是一串红点。
原文中还有一个MIN宏定义的正确用法
|
|
接下来再把我使用的log宏放出来
|
|
这个宏的意思请分别在release 和 debug下测试
#undef
当你使用了#define
宏定义后,则在整个程序的运行周期内这个宏都是有效的,但有时候我们在某个逻辑里希望这个宏失效不想使用,则会使用
条件编译
#if #else #endif
如果#if 之后的条件语句成立的话
编译#else
里面的代码 反之编译 #else
之后的代码 #endif
结束语句
接下来是一些预定的宏
DEBUG
DEBUG环境下返回YES__has_feature(objc_arc)
开启ARC时返回YES__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
如果手机系统小于7.0 返回YES,版本策略
#if defined #ifdef #ifndef #elif
#if defined (AAA)
: 如果已经定义了AAA这个宏,返回YES,可用于复杂条件#if defined (AAA) && defined (BBB)
或者#if defined (AAA) || VERSION > 12
#iddef (AAA)
: 如果已经定义了AAA这个宏,返回YES,单个条件#ifndef
: if not defined 的缩写#elif
: 跟#if
一起使用,else if的缩写
错误、警告处理
#### #error
如果编译器遇到这货,马上就会罢工。
如果你在开发一些arc only的库,那么一下代码就有了作用
|
|
#warning
这个用法很简单,只要后面跟上你想警告的话就OK了,这样你就可以让编译器提醒这个警告。这个我经常用。但是还有一个更好用的TODO
脚本,一会而我会放出来。
编译器控制
#pragma
这个应该算是使用的非常多的指令了吧,应该所有的程序员都应该知道代码#pragma mark
的作用。
#pragma mark
这个真没有什么好说的了,记得#pragma mark
和#pragma mark -
的区别就好了
#pragma
非常复杂需要你对编译器底层非常的了解,只有当你开发一些比较底层的framework的时候才可能比较多用的。 Clang使用手册
#pragma message(“”)
可以输出调试信息
|
|
如
|
|
如果没有被使用的时候不会报出警告
关于警告一类的文章可以看看喵神王巍的博客。
其他
#####line
这个就没什么好说的,如果你自定义过NSLog 或者看到其他自定义的log并且点进去看过的话应该会看得到__line__
这个东西吧,这表示本行语句在源文件中的位置信息。而#line
就是可以改变当前行的行号在编译器中的表示。并且之后的行号也会相应的改变。比如
|
|
输出为,如果第一个printf在第三行的话
|
|
结语
刚刚开始写博客,这是我水的第二篇博文了,依然是那么的水,主要还是为了想要测试一下HEXO的玩法。以后希望自己能够在javascript 、python 、 React Native 这三个方向进步。写这个博客也是为了学习一下javescript,结果几乎没有用到js的知识。以后写一些有关于这三个方向的学习心得或者说是学习笔记吧。这篇文章发出来之后,博客的基本用法也就学的七七八八了,期待自己的进阶之旅,也期待自己在iOS的进阶之旅,也期待自己在is py rn这三个方向的入门之旅。
哦, 对了那个TODO是这样设置的:
第四步中的脚本为:
|
|
原理是根据正则表达式去判断。
用起来是这样的: