iOS编译与链接六:预编译指令和Clang 语言扩展
一.预编译指令
预编译指令一般分为文件包含,条件编译,宏定义,和编译选项
1.文件包含
关于文件包含在上一篇
2.条件编译
#if
#elif
#else
#endif
这四个就是if else的条件控制,其中后面跟的语句非0则为真,和OC一样
#ifdef
表示如果定义了一个宏,只是查找有没有这个宏,与宏是不是定义了替换部分,替换部分是不是非0无关
#ifndef
与上面相反,表示如果没有定义这个宏
条件编译必须以#if或#ifdef或#ifndef开始,以 #endif结束
3.宏定义
祭出以前学习过的喵神的文章
这篇文章主要讲了两件事:
一是宏定义是单纯的文本替换,需要时刻注意替换后会变成什么样子,宏定义的时候能包的多严实就包的多严实,甚至还有用do while这种用代码段来包含宏定义的方式.
二是介绍了一些常用的预定义,预定义属于GNU C的扩展,并且基本都被Clang继承过来,后面介绍.
二.一些宏的用法和内置宏
2.拼接宏定义字符串
在宏定义的字符串中引用其他宏,需要用""包裹
在C中拼接字符串可以直接拼
#define str1 "114"
#define str2 "514"
#define str ""str1""str2""
OC中需要在宏后面加上@
#define str1 @"114"
#define str2 @"514"
#define str @""str1@""str2@""
3.#操作符
操作符可以实现将宏定义的参数转化成字符串
#define Msg(num) NSLog(@"%s %d", #num, num+1)
Msg(2);
///输出: 2 3
或者拼接宏定义字符串
#define Msg(num) NSLog(@""#num" %d", num+1)
需要注意宏替换不会执行表达式,参数是什么,就转换什么,这个方法可以直接输出变量名,方法名
Msg(1+1);
//输出: 1+1 3
- (int)getNum{
return 2;
}
int a = [self getNum];
Msg(a);
//输出: a 3
Msg([self getNum]);
//输出: [self getNum] 3
4.##操作符
可以连接两个符号组成一个新的符号
#define SS a##b
int SS = 10;
编译器会转换成ab,此时警告显示:Unused variable 'ab'
需要注意的是这并非字符串操作,本质还是文本替换.只不过具有动态性,可以写出动态的变量名,方法名
#define Num(A, B) A##B
NSLog(@"%d",Num(2, 3));
//输出23
#define autoIndex(i) index##i
int autoIndex(0) = 10;
int autoIndex(1) = 20;
if(arc4random_uniform(2) % 2){
NSLog(@"%d",autoIndex(1));
}else{
NSLog(@"%d",autoIndex(0));
}
5.换行
使用反斜线\换行;
宏的换行与代码书写习惯无关,想换就可以换,只要不把符号本身朝开就行.
#define autoIndex(i) index\
##i
#define logNum if(arc4random_uniform(2) % 2){ \
NSLog(@"%d",autoIndex(1)); \
}else{ \
NSLog(@"%d",autoIndex(0)); \
}
int autoIndex(0) = 10;
int autoIndex(1) = 20;
logNum;
6.文件,行号,方法名
FILE 文件名
LINE 行号
func 方法名
7....和VA_ARGS
...表示可变参数,VA_ARGS用于对应宏定义的中的可变参数
#define Log(...) NSLog(__VA_ARGS__)
如果还有其他参数,比如
#define Log(format, ...) NSLog(format, ##__VA_ARGS__)
则需要用##连接起来,除了连接,当...是0个参数时,##还能吧前面多出来的逗号去除.
比如这个扩展NSLog的宏
#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
do while (0)只执行一次,目的是包裹代码段
fprintf(stderr,是格式化输出函数,用来输出附加信息
(NSLog)((format), ##VA_ARGS); 是用来对应NSLog格式化输出的,##把前后连成一个表达式.
8.OBJC
这个宏表示OC文件安全引用,用这段预编译代码包裹,不能兼容的里面的objc code的文件,比如.c,.cpp等可以安全的引用这个文件,编译时会忽略里面的内容.
#if __OBJC__
//objc code
#endif
9.OBJC2
表示objc2.0,他的值目前是1
可以编译一下看看
//A.h
#define MH_MAGIC 114
#if __OBJC2__
#define MH_CIGAM 514
#else
#define MH_CIGAM 5141919810
#endif
#endif /* A_h */
//A.c
int main(){
return MH_MAGIC + MH_CIGAM;
}
clang -E A.c
输出
int main(){
return 114 + 5141919810;
}
10.LP64
指64位运行环境
#if __LP64__ || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
可以看到64位的NSInteger是long的别名,而32位是int
11.COUNTER
效果是自增,具有唯一性,每一次使用都会自增
三.GCC和Clang 语言扩展
1. typeof(A)
用于获取A的类型
int a ;
__typeof__(a) b;
用A的类型定义一个b
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
这段是GNU C中MIN的宏定义,
typeof(A) __a = (A);是获取A的类型,然后定义一个a,赋值上A.这么做是为了防止A在后面的代码中发生变化,所以暂存A和B的值.
2.attribute
attribute 可以用于修饰函数,类型,变量
语法格式是attribute ((attribute-list)),attribute-list可以有多个,用逗号隔开.
这个attribute-list有一大堆,主要有函数属性(Function Attribute),类型属性(Type Attributes),变量属性(Variable Attribute)三种类型,加上两个Clang扩展的availability和overloadable.
修饰对象,结构体,枚举类型时,类似const这一类关键词,放在前面;修饰函数时,放在后面,并且在;之前
attribute的目的主要是为了告诉编译器它该如何去做,如何优化,程序员根据具体的代码实现,添加上合适的attribute来指导编译过程的优化.
- 一些函数类型说明
always_inline; noinline; flatten; pure 与内联函数相关,指导编译器是否进行内联处理.
sentinel 可变参数需要以NULL作为最后一个.
format 让编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配,format还有具体的语法:
format (archetype, string-index, first-to-check)
archetype是格式;string-index指第几个参数是格式化字符串;first-to-check表示可变参数中的第一个在全部的参数中是第几个.
unused 函数未使用也不会报警告
used 告诉编译器这个一定会用到,防止未使用的函数被dead code striping过滤掉
visibility(“visibility_type”) 函数符号相关
visibility_type有四种
default: 可见性是默认的符号链接可见性,如果我们不指定visibility 属性,那么默认就使用默认的可见性。默认可见性的对象与函数可以直接在其他模块中引用.
hidden: 符号不存放在动态符号表中,因此,其他可执行文件或共享库都无法直接引用它。使用函数指针可进行间接引用。
internal: 除非由特定于处理器的应用二进制接口 (psABI) 指定,否则,内部可见性意味着不允许从另一模块调用该函数。
protected: 该符号存放在动态符号表中,但定义模块内的引用将与局部符号绑定。也就是说,另一模块无法覆盖该符号。
constructor 指定函数在main函数之前调用,可以增加优先级,需要大于100,比如constructor(101)
比如在swift中使用+load方法的一直替代方案
static void __attribute__ ((constructor)) testLoad() {
[ [NSNotificationCenter defaultCenter] addObserver:NSClassFromString(@"TestClass")
selector:NSSelectorFromString(@"appStart")
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
class TestClass: NSObject {
@objc static func appStart() {
}
}
destructor指定函数在main函数返回之后调用,同样可以添加优先级.在app中,main函数返回意味着进程将要结束.也就是相当于在进程将要结束的时候调用.
noreturn 表示函数不需要返回值,一些情况下函数在没有进行到return的时候就退出了,此时也不会抛出异常.
warn_unused_result 表示必须使用返回值,比如init方法.不使用返回值会警告.
- 变量属性
weak 弱定义符号,若两个或两个以上全局符号名字一样,而其中之一声明为弱定义符号,则这些全局符号不会引发重定义错误。链接器会忽略弱符号,去使用普通的全局符号来解析所有对这些符号的引用,但当普通的全局符号不可用时,链接器会使用弱符号。当有函数或变量名可能被用户覆盖时,该函数或变量名可以声明为一个弱符号.
weak也可以修饰函数.
weakref(“target”) 弱引用符号,弱引用时如果需引用符号不存在也不会链接出错,而是将需要引用的符号定义为WEAK属性及0地址.
cleanup 修饰一个变量,在作用域结束时,调用一个指定的函数.在括号中添加函数cleanup(func)
objc_runtime_name 再编译的时候把类名或者协议名称改成另一个,可以用来做混淆,
__attribute__((objc_runtime_name("Test2")))
@interface TestObjc : NSObject
@end
NSLog(@"%@", NSStringFromClass([TestObjc class])); // "Test2"
- clang扩展的
__has_attribute 用于检查一个attribute属性是否可用
__has_feature 用于检查一个预定义是否可用,
例如#if __has_feature(objc_arc),是否支持rac
__has_feature(ptrauth_calls) 指arm64e架构
availability系统版本限制,其中有很多参数:
introduced是支持的最低版本;deprecated在这个版本开始废弃(但还能用);obsoleted不能使用了;unavailable也是不能使用;message编译器警告信息.
overloadable 用于支持对GNU C标准库函数的重载
unavailable 表示不可以使用,调用会报错,系统定义了内置宏:
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE