加入收藏 | 设为首页 | 会员中心 | 我要投稿 河北网 (https://www.hebeiwang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程 > 正文

C语言全局变量那些事儿

发布时间:2018-04-17 02:59:15 所属栏目:编程 来源:酷壳-coolshell
导读:(感激网友@我的上铺叫路遥投稿) 作为一名措施员,假如说入神一门编程说话算作一种爱好的话,那么与此同时反过往复黑一门编程说话就是这种爱好的升华。本日我们就来黑一把C说话,好好展示一下这门经典说话令人抓狂的一面。 我们知道,全局变量是C说话语法
副问题[/!--empirenews.page--]

(感激网友 @我的上铺叫路遥 投稿)

作为一名措施员,假如说入神一门编程说话算作一种爱好的话,那么与此同时反过往复黑一门编程说话就是这种爱好的升华。本日我们就来黑一把C说话,好好展示一下这门经典说话令人抓狂的一面。

我们知道,全局变量是C说话语法和语义中一个很重要的常识点,起首它的存在意义必要从三个差异角度去领略:对付措施员来说,它是一个记录内容的变量(variable);对付编译/链接器来说,它是一个必要理会的标记(symbol);对付计较机来说,它也许是具有地点的一块内存(memory)。其次是语法/语义:从浸染域上看,带static要害字的全局变量范畴只能限制在文件里,不然会外联到整个模块和项目中;从保留期来看,它是静态的,贯串整个措施或模块运行时代(留意,正是跨单位会见和一连保留周期这两个特点使得全局变量每每成为一段受进攻代码的打破口,相识这一点异常重要);从空间分派上看,界说且初始化的全局变量在编译时在数据段(.data)分派空间,界说但未初始化的全局变量暂存(tentative definition)在.bss段,编译时自动清零,而仅仅是声明的全局变量只能算个标记,寄存在编译器的标记表内,不会分派空间,直到链接可能运行时再重定向到响应的地点上。

我们将向您揭示一下,非static限制全局变量在编译/链接以及措施运行时会产生哪些风趣的工作,趁便可以对C编译器/链接器的理会道理管中窥豹。以下示例对ANSI C和GNU C尺度都有用,笔者的编译情形是Ubuntu下的GCC-4.4.3。

第一个例子

Makefile如下:

运行环境:

这个项目里我们界说了四个全局变量,t.h头文件界说了一个整型a,main.c里界说了两个整型b和c而且未初始化,foo.c里界说了一个初始化了的布局体,还界说了一个main的函数指针变量。因为C说话每个源文件单独编译,以是t.h别离包括了两次,以是int a就被界说了两次。两个源文件里变量b和函数指针变量main被一再界说了,现实上可以看做代码段的地点。但编译器并未报错,只给出一条告诫:

运行措施发明,main.c打印中b巨细是4个字节,而foo.c是8个字节,由于sizeof要害字是编译时决策,而源文件中对b范例界说纷歧样。但令人诧异的是无论是在main.c照旧foo.c中,a和b都是沟通的地点,也就是说,a和b被界说了两次,b照旧差异范例,但内存映像中只有一份拷贝。我们还看到,main.c中b的值居然就是foo.c中布局体第一个成员变量b.a的值,这证实了前面的揣度——即便存在多次界说,内存中只有一份初始化的拷贝。其它在这里c是置身事外的一个独立变量。

为何会这样呢?这涉及到C编译器对多重界说的全局标记的理会和链接。在编译阶段,编译器将全局标记信息隐含地编码在可重定位方针文件的标记内外。这里有个“强标记(strong)”和“弱标记(weak)”的观念——前者指的是界说而且初始化了的变量,好比foo.c里的布局体b,后者指的是未界说可能界说但未初始化的变量,好比main.c里的整型b和c,尚有两个源文件都包括头文件里的a。当标记被多重界说时,GNU链接器(ld)行使以下法则决策:

  • 不应承呈现多个沟通强标记。
  • 假若有一个强标记和多个弱标记,则选择强标记。
  • 假若有多个弱标记,那么先决策到size最大的谁人,假犹如样巨细,则凭证链接次序选择第一个。

像上面这个例子中,全局变量a和b存在一再界说。假如我们将main.c中的b初始化赋值,那么就存在两个强标记而违背了法则一,编译器报错。假如满意法则二,则仅仅提出告诫,现实运行时决策的是foo.c中的强标记。而变量a都是弱标记,以是只选择一个(凭证方针文件链接时的次序)。

究竟上,这种法则是C说话里的一个大坑,编译器对这种全局变量多重界说的“纵容”很也许会无故修改某个变量,导致措施不确定举动。假如你还没故意识到局势严峻性,我再举个例子。

      第二个例子

运行环境如下:

(声名一点,运行环境是直接输出到stdout的打印,笔者曾经将./test输出重定向到log中,功效发明打印的执行序列纷歧致,以是回收默认输出。)

这是一个多历程情形,起首我们看到无论父历程照旧子历程,main.c照旧foo.c,全局变量b和c的地点如故是同等的(虽然只是个逻辑地点),并且对b的巨细差异模块如故有差异的决策。这里值得留意的是,我们在子历程中对变量b举办赋值举措,以后子历程自己包罗foo()挪用中,整型b以及布局体成员b.a的值都是1,而父历程中整型b和布局体成员b.a的值还是2,但它们表现的逻辑地点还是同等的。

小我私人以为可以这样表明,fork建设新历程时,子历程得到了父历程上下文“镜像”(天然包罗全局变量),假造地点沟通但属于差异的历程空间,并且此时真正映射的物理地点中只有一份拷贝,以是b的值是沟通的(都是2)。随后子历程对b改写,触发了操纵体系的写时拷贝(copy on write)机制,这时物理内存中才发生真正的两份拷贝,别离映射到差异历程空间的假造地点上,但假造地点的值自己如故稳固,这对付应用措施来说是透明的,具有遮盖性。

尚有一点值得留意,这个示例编译时没有呈现第一个示例的告诫,即对变量b的sizeof决策,笔者也不知道为什么,或者是GCC的一个bug?

       第三个例子

这个例子代码同上一个同等,只不外我们将foo.c做成一个静态链接库libfoo.a举办链接,这里只给出Makefile的窜改。

运行环境如下:

从这个例子看不出有啥不同,只不外行使静态链接后,全局变量加载的地点有所改变,b和c的地点之间好像相隔更远了些。不外这次编译器倒是给出了变量b的sizeof决策告诫。

到此为止,有些人也许会对上面的例子嗤之以鼻,认为这不外是罗列了C说话的某些特征罢了,算不上黑。有些人以为既然云云,对付统统全局变量要么用static限死,要么界说同时初始化,杜绝弱标记,以便在编译时报错检测出来。只要警惕地行使,C说话照旧很美满的嘛~对付抱这样设法的人,我只想说,请你在夜深人静的时辰竖起耳朵细心凝听,你很也许听到Dennis Richie在地府之下险恶的笑声——不,与其说是讥笑,不如说是谩骂……

       第四个例子

Makefile剧本:

执行功效:

(编辑:河北网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读