从在.h头文件中赋值并初始化static变量谈谈预编译和链接

写这篇文章的原因:

之前组里一个队友在写OC的时候,在OC的.h头文件里初始化并赋值了一个静态的字符串变量,并在多个.m实现文件引入了这个头文件然后使用了这个静态成员变量(虽然这种做法不提倡),导致程序出现了一些奇奇怪怪的行为(每个.m文件中的静态变量都不一样),他找了很久也百思不得解。这里我就针对这个问题谈谈预编译过程,以及浅谈相关的编译和链接的一些知识,希望对大家有帮助,也欢迎大神吐槽拍砖:

文章结构图.png
在.h中添加静态变量的情况如下:
s1.png

那我们开始,首先普及一下概念

静态变量:

-----首先普及一下加上 static 关键字的静态变量的特性:OC 是基于 C 语言的,在C语言中一个变量加上 static 关键字代表这个变量是只能在当前文件内使用的,并且在当前文件内同名的静态成员变量只能有一个,如果有多个那么后初始化并赋值的静态变量会把之前同名的覆盖。

预编译:

然后简单讲讲预编译:预编译就是在编译之前由IDE(对我们来说就是XCODE)对文件进行的一些处理,其中包括:1.宏替换(#define); 2.文件包含(#import) ;和 3.条件编译(#if #endif); 三项。这里只介绍概念,后文会细说。当然这个阶段还会对我们写的注释进行处理比如 #pragma 。通俗的说预编译就是对带 “井” 前缀(#***)的部分进行处理和简单的替换操作。那么以下我就针对开头我说的,在头文件中引入静态变量问题开始讲:

1.首先讲讲本次测试用的类的结构和方法,如下图:

  • 首先是 ContainStringClass 类的 .h 文件中有一个静态变量方法:
    static NSString *staticString = @“origin”;
  • 然后是它的两个方法,一个是改变静态变量 staticString 值的方法:
    - (void)changeStaticStringTo:(NSString *)string;
  • 另一个是内部打印静态变量 staticString 值的方法:
    - (void)logContainStringClass:(NSString *)index
ContainStringClass.h

这是 ContainStringClass 类的.m实现文件

ContainStringClass.m

然后是 ViewController 的 ViewDidLoad() 方法中的代码,目的是为了测试和打印

viewController.png

2.下面讲讲以上两个类的作用:

以上测试用的两个类。代码的功能就是:

  • 首先在 ContainStringClass.h 里初始化并赋值静态变量 static NSString *staticString = @"origin”;
  • 然后在 ViewController.m 中 #import ContainStringClass.h 并打印 staticString 的值;
  • 接着在 ContainStringClass 类内部修改这个静态变量的值然后打印;
  • 最后在 ViewController.m 文件中修改这个静态变量的值并打印。
    以下是打印结果:
打印结果

怕大家看不清附上打印结果放大版

打印结果放大版.png

3.下面解释一下输出结果

可以看到,虽然在 ViewController.m 中和 ContainStringClass.m 里边访问 “同一个” 在 ContainStringClass.h 中声明的静态变量,可是它们的值是互不影响的,仅在最初他们的值一样都是 staticString 的初值 @“origin” 。
在 ViewController.m 中对静态变量 staticString 的修改仅在 ViewController.m 中有效;同样的,在 ContainStringClass.m 中的修改仅在 ContainStringClass.m 中有效;

为什么呢?下面我们来说明:
  • ①. 首先,在预编译阶段,#import 就是文件包含,预编译对文件包含的处理非常简单,就是文本(代码)拷贝。所以在这里其实就是把 ViewController.m 中 #import ContainStringClass.h 这条语句给替换成 ContainStringClass.h 文件里的内容(代码),其中包括了 static NSString *staticString = @"origin” 这条语句。
  • ②. 同时,在 ContainStringClass.m 文件中也有 #import ContainStringClass.h 这条语句。也仅仅是简单的文本拷贝过来,所以这里也包含了 static NSString *staticString = @“origin” 这条语句。
  • ③. 现在明白什么是文件包含了吧,预编译的文件包含导致本来在 ContainStringClass.h 中的静态变量赋值语句 static NSString *staticString = @“origin”; 被拷贝到 ContainStringClass.m 和 ViewController.m 文件中且各有了一份代码;而由静态变量的特性可以知道静态变量在每个文件内部是唯一的(即每个文件中同名的静态变量仅有一份内存);所以最后演变成 ContainStringClass.m 和 ViewController.m 中各有一个名为 staticString 的静态变量,也就导致了上面的互不影响的打印结果(有点像两个方法中各有一个同名局部变量,只是静态变量作用域是在文件内生命周期也和app一样)。

4.下面我们再来讲讲预编译:

  • 预编译是 IDE 做的简单文件处理(就是针对 # 符号打头的代码的处理).
  • 文件包含:其中 #include 和 #import 是文件包含,而 #import 会对重复包含的文件做去重的处理,文件包含其实就是把文件中的内容(代码)做简单拷贝(.h文件不参与编译,预编译过后.h的内容都被拷贝到.m中了);
  • 然后是宏替换,宏替换就是把 #define 定义和使用的宏都替换成原来的代码,即把宏都干掉;
  • 最后是条件编译 #if #else #endif ,条件编译就是只留下条件为 ‘真’ 部分的代码,即只留下 #if 内的代码或者 #else 内的代码。
  • 最最后还有我们常用的注释 #pragma 在预编译后会被干掉(注释不参与编译);

5.然后我们谈谈编译:

  • 这里我们把编译细分成编译汇编,。如果是 OC 的话,在编译后会先生成 C 代码(因为 OC 是对 C 的封装),然后生成汇编代码(如果是 Swift 就是 Swift => C++ => 汇编 => 机器码 )。
  • 再之后汇编阶段会把汇编代码生成机器码。在编译完成之后会生成符号表,简单理解符号表:用全局变量举个例子,我们知道全局变量只有一份,但是很多类都可能要使用它,所以每个使用全局变量的地方都要有一个方法来找到这个全局变量在内存中的位置。
  • 换句话说就是每个引用这个全局变量的地方都要有一个方式去找到这个全局变量在哪,这个提供找全局变量的方式就是符号表。其实编译器会为每一个引用外部符号(比如全局变量)的地方提供一个方式找到这个外部符号,这就是符号表了。

6.然后是链接:

  • 链接就是把编译完成的所有文件合并成一个可执行文件(编译过后会生成一堆单个的 .o 后缀的文件)。
  • 链接阶段会处理符号表,在合并所有文件过程中,肯定涉及到合并的顺序,可能出现的情况是:使用全局变量的类已经合并完成了,但是给全局变量初始化和赋值的类文件还没有合并进来。那么此时怎么办呢?
  • 这就要靠符号表了,当发现要使用的全局变量找不到,就告诉链接器:“我这里需要一个全局变量,名叫XXX”,然后等全局变量初始化和赋值的类被链接的时候发现有一个全局变量,此时会告诉链接器:“我这有一个名叫XXX的全局变量要加入进来了”;然后链接器会试图把它们相匹配上;
  • 链接器也会把代码,全局变量,静态变量这些东西都区分开并分配地址(相对地址)空间,这就是重定向

7.链接之后的就是可执行程序了

  • Linux上可执行文件后缀是.ELF,MAC OS和iOS上是 Mach-O,但是可能被隐藏了,也就是没有后缀。
  • 首先,要执行的时候操作系统会分配一个进程(分配一个PCB进程控制块)并分配虚拟内存。
  • 然后,分配好之后操作系统调用加载器把代码复制到这块内存后跳转到程序入口点(其实实际是真的执行到某一部分代码的时候才把那部分的代码和数据加载进来,有点像懒加载)。
  • 接着,调用启动函数,启动函数会调用系统启动函数以初始化执行环境。
  • 初始化完成之后调用 main 函数并传入命令行参数和环境变量(下图的argc为命令行参数个数,argv[]为命令行参数数组)。
  • 最后就是我们熟悉的 UIApplicationMain 程序循环的启动了。
main函数

8.拓展:

根据以上知识知道预编译的意思了吧,那么如果:

  • ①.在多个 .h 文件中声明同名静态变量会怎样呢?
    答案是 XCODE 会给你检查出来并报错;
  • ②.如果在多个 .h 文件中初始化并赋值全局变量呢?
    答案是 XCODE 也能检查出来;
  • ③.如果在多个 .h 文件中声明但是不赋值一个全局变量呢?比如:
    NSString *globalString;
    答案是这样XCODE 是检查不出来的,但是链接阶段会出错。这是链接阶段的选项决定的,如果想设置也可以修改以允许多个未赋值的全局变量。下图是未赋值的意思(形容不好如果造成了误导别拍砖,拍也别拍脸>_<):
s10.png

最后:

如果修改链接器参数是可以容忍多个未赋值的全局变量的(但是别这么干,因为每次用具体用哪个就不知道了,估计为了安全现在主流IDE都是不允许任何形式的多个全局变量);
如果对计算机和执行过程感兴趣推荐大家看看 《深入理解计算机系统》这本书,有点难,内容也有点多但是写的很好。有兴趣希望能一起多交流。Have fun~~!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,125评论 29 470
  • 1.OC里用到集合类是什么? 基本类型为:NSArray,NSSet以及NSDictionary 可变类型为:NS...
    轻皱眉头浅忧思阅读 1,363评论 0 3
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,779评论 0 27
  • 凌霄之龙(二十四)力战巨怪 福娃著 【作者简介】福娃,杨炅灵,十一岁,小学六年级学生。爱好广泛,尤喜文字。在国家期...
    福娃蜜妈阅读 287评论 7 16
  • 本以为这周会一如既往的平平淡淡的度过 但是被我妈的一通电话搅出了涟漪 我姐和姐夫一毕业就结婚了 奉子成婚 姐夫家...
    LynneLYL阅读 527评论 0 1