函数或全局变量重复定义时会怎样?

数据库2025-11-05 11:18:0784

 

 本文转载自微信公众号「编程珠玑」,函数或全会样作者守望先生。局变转载本文请联系编程珠玑(ID:shouwangxiansheng)公众号。量重 

可能有些朋友第一反应是复定,那肯定是函数或全会样编译不过喽:

// 来源:公众号【编程珠玑】 // 作者:守望先生 // fun.c #include<stdio.h> void fun() {     printf("编程珠玑n"); } // main.c #include<stdio.h> void func() {     printf("公众号n"); } int main(void) {     func();     return 0; } 

编译:

$ gcc -o main main.c fun.c /tmp/ccKeACRk.o: In function `fun: fun.c:(.text+0x0): multiple definition of `fun /tmp/cc4ezgqh.o:main.c:(.text+0x0): first defined here collect2: error: ld returned 1 exit status 

可以看到这里报错了,因为fun重复定义了。局变

但是量重重复定义就会报错,会编译不过吗?复定不全是!

再看下面的代码:

// 来源:公众号【编程珠玑】 // 作者:守望先生 //var.c int num; void change() {     num = 1023; } //main.c #include<stdio.h> void change(); int num = 1024; int main(void) {     printf("before:num is %dn", num);     change();     printf("after:num is %dn", num);     return 0; } 

输出结果:

before:num is 1024  after:num is 1023  

从结果中可以看到,虽然num被定义了两次,函数或全会样但是局变仍然可以编译通过,并且正常运行。量重这又是复定为什么呢?

符号

在说明今天重点分享的内容之前,先简单了解一下什么是函数或全会样符号。在《hello程序是局变如何变成可执行文件的》中讲到过,ELF文件生成的量重最后阶段会经历链接,而链接阶段正是基于符号才能完成。每个目标文件都会有一个符号表。而链接过程正是通过符号表中的符号,将不同的高防服务器目标文件“粘”在一起,形成最后的库或者可执行文件。要查看一个目标文件的符号信息也很容易:

// symbol.c #include<stdio.h> int symbol = 1024; int func_symbol() {     return 0; } 

编译:

$ gcc smbol.c #编译 $ nm symbol.o #查看符号信息 0000000000000000 T func_symbol 0000000000000000 D symbol 

通过nm命令就可以查看符号信息,这里就有我们的func_symbol函数和全局变量symbol的符号。

关于nm的使用,在《几个命令了解ELF文件的秘密》也有介绍。

除了上面提到的全局符号,目标文件中还有其他符号信息,不过这不是本文关注的重点。

强符号与弱符号

对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。当然也可以通过

__attribute__((weak)) 

来定义一个强符号为弱符号。

通过下面的例子来看看哪些是强符号,哪些是弱符号:

#include<stdio.h> int weak; // 未初始化全局变量,弱符号 int strong = 1024; // 已初始化全局变量,强符号 __attribute__((weak)) int weak1 = 2222; // 使用标识修饰的弱符号 int main(void) {     printf("编程珠玑n");     return 0; } 

注意,这里的企商汇强符号与弱符号都是针对定义来说的。

同名时,用哪个?

对于多重定义,即标题提到的变量重名时,链接器有它的处理规则:

1.强符号不允许重复 2.有一个强符号和多个弱符号,使用强符号 3.多个弱符号,则随意选择一个

关于第一点,在最开始的例子中你已经见到了,最常见的情况就是你重复定义了变量或者函数等等。

而第二点也有示例,示例中,虽然定义了两个num,但是var.c中未初始化的num是弱符号,main.c中的num是强符号,这种情况下编译正常。只是最终会使用强符号的num。

再看一个第三点的例子也是类似,免费源码下载当其中main.c的num无初始化时,也是可以编译过的。这种情况下的误用也就罢了,如果是重复的符号,但是类型不同,问题就更大了,即var.c的内容如下:

//var.c double num; void change() {     num = 1023; } 

这里的num变成了double,再次编译运行,你会发现意想不到的结果:

before:num is 1024  after:num is 0  

为什么修改后是0?原因在于double类型的数据存储与int类型数据存储格式不一样(参考《对浮点数的一些理解》),且它们占用空间长度都不一样,在本文例子中,double占用8字节,而int占用4字节。

总之,这不是我们想要的结果,最终的后果可能比我们想象的要严重,要更难发现。

总结

如非特殊需求,应该尽量避免出现全局变量同名,以免造成意料不到的结果,例如使用变量时最小范围定义,即尽可能避免全局变量,或者使用命名空间(如C++中)。

当然了,强弱符号在某些时候是非常有用的,例如制作库以支持用户自定义的库,这又该怎么做呢?敬请期待下一篇。

参考

参考书籍

《深入理解计算机系统》

《程序员的自我修养》

本文地址:http://www.bzuk.cn/news/205e31399481.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

小心谨慎或灾难体验会让每一个系统管理都认识到频繁的系统备份的重要性。你可以通过编写管用的旧式 shell 脚本,或使用一个(或几个)适合这项工作的备份工具来完成备份任务。因此,当你要实施一个备份解决方案时,你了解的备份工具越多,你做出的决策就会越明智。在该文中,我们将为你介绍 backupninja ,这是一个轻量且易于配置的系统备份工具。在诸如 rdiff-backup, duplicity, mysqlhotcopy 和 mysqldump 等程序的帮助下, Backupninja 可以提供常用的备份功能,如执行远程的、安全的和增量式的文件系统备份,加密备份以及 MySQL/MariaDB 数据库备份。你可以选择性地开启使用 Email 状态报告功能,也可以对一般的硬件和系统的信息进行备份。 backupninja 的一个关键功能是它拥有一个内建的基于控制台的向导程序(被称为 ninjahelper),而后者允许你为不同的备份情景轻松地创建配置文件。假如非要说的话,backupninja 的缺点是:为了充分使用其所有的功能,它要求安装一些其他“助手”程序。尽管 backupninja 有针对基于 Red Hat(红帽)的发行版本的 RPM 安装包,但 backupninja 针对 Debian 及其衍生发行版本的依赖进行了优化。所以不建议在基于 Red Hat 的系统上尝试 backupninja 。在这篇教程中,我们将介绍如何在基于 Debian 的发行版本上安装 backupninja 。安装 Backupninja以 root 账户来运行下面的命令:复制代码代码如下: # aptitude install backupninja 在安装的过程中,有几个文件和目录将被创建:     /usr/sbin/backupninja 是个 bash shell 的主脚本; /etc/cron.d/backupninja, 默认情况下,设置 cron 任务来每隔一个小时运行上面的主脚本; /etc/logrotate.d/backupninja 截断由 backupninja 程序产生的日志; /etc/backup.d/ 是备份操作的配置文件驻留的目录; /etc/backupninja.conf 是包含一般选项的主配置文件。这个文件带有良好的注释且详细解释了每个选项的含义; /usr/share/backupninja 是那些 backupninja 所使用的脚本所处的目录。这些脚本文件负责执行实际的工作。在这个目录中,你还可以找到 .helper 文件,它们可以被用来配置和设定 ninjahelper 的菜单; /usr/share/doc/backupninja/examples 含有操作配置文件(即通过 ninjahelper 产生的文件)的模板。首次运行 Ninjahelper当我们尝试启动 ninjahelper 时,我们可以看到可能需要一个内部依赖程序。假如系统进行了提示,请输入 “yes” 并敲下回车键来安装 dialog(一个用于从 shell 脚本中显示友好对话框的工具)。当你在键入 yes 后再敲回车键时,backupninja 将会安装 dialog,一旦安装完成,将呈现出下面的截屏:案例 1: 备份硬件和系统信息在启动了 ninjahelper 之后,我们将创建一个新的备份操作:假如必要的助手程序没有被安装,下面的截屏将会呈现在我们眼前。假如这些软件包已经在你的系统上安装了,请跳过这一步。接下来的一步需要你选取相关条目来作为此次备份任务的一部分。前四个条目已经默认被选上了,但你可以通过在条目上按空格键来撤消选择。一旦你完成了上面的步骤,按 OK 选项来继续。接着你将能够选择是愿意使用默认的配置文件(/etc/backup.d/10.sys)来完成这次备份操作,还是创建一个新的配置文件。若为后者,一个含有与默认配置文件内容相同的文件将会在相同的目录下被创建,但它被命名为 11.sys,后续的备份操作将会创建类似的文件(注:只不过命名的序号不同)。需要说明的是一旦这个新的配置文件被创建,你便可以使用你喜爱的文本编辑器来编辑该文件。案例 2: 一个远程目录的增量式 Rsync 拉取备份正如你最有可能知道的那样, rsync 被广泛地用于通过网络同步文件或文件夹。在接下来的例子中,我们将讨论一个使用硬链接来为一个远程目录做增量式拉取备份的方法,它被用来保存历史数据以及在我们本地的文件服务器中恢复这些历史数据。这个方法将帮助我们节省空间并增强位于服务器端的安全性。步骤 1:编写一个带有如下内容的自定义脚本,放在 /etc/backup.d,并将它的权限设置为 600 。需要说明的是,除了一般的配置文件,这个目录可能还包含当 backupninja 执行时你想运行的一些脚本文件,它们可以发挥出位于主配置文件中的变量的优势。复制代码代码如下:# REMOTE USER user=root # REMOTE HOST host=dev1 # REMOTE DIRECTORY remotedir=/home/gacanepa/ # LOCAL DIRECTORY localdir=/home/gacanepa/backup.0 # LOCAL DIRECTORY WHERE PREVIOUS BACKUP WAS STORED localdirold=/home/gacanepa/backup.1 mv $localdir $localdirold # RSYNC rsync -av --delete --recursive --link-dest=$localdirold $user@$host:$remotedir $localdir在上面的配置中, rsync 的 ‘--link-dest’ 选项的作用是为位于 $localdir-old 目录中那些没有改变的文件(包含所有属性) 硬链接到目标目录($localdir)。步骤 2:在 backupninja 第一次运行之前,上层目录(这个例子中指的是 /home/gacanepa) 是空的。第一次我们执行下面的命令:复制代码代码如下:# backupninja -nbackup.0 目录就被创建了,并在接下来的过程中,它的名称将会被更改为 backup.1。当我们第二次运行 backupninja 时, backup.0 将会被重新创建,而 backup.1 保持不动。步骤 3: 确保 backup.1 里面的文件硬链接到 backup.0 里的文件,我们可以通过比较文件的 inode(i 节点)数和目录的大小来达到此目的。总结Backupninja 不仅是一个经典的备份工具,它也是一个易于配置的实用程序。你可以通过编写你自己的控制脚本,用放在 /etc.backup.d 中的不同的配置文件来运行 backupninja 。甚至你还可以为 ninjahelper 编写助手程序,并将其包括在 ninjahelper 的主界面上。例如,假如你在 /usr/share/backupninja目录中创建了一个名为 xmodulo 的控制脚本,它将自动运行那些位于 /etc/backup.d 目录中以 .xmodulo 为后缀的每个文件。假如你决定添加你的 xmodulo 控制脚本到 ninjahelper 中, 你可以编写相应的助手程序,即 xmodulo.helper 。另外,假如你想 让 backupninja 运行其它的脚本,只需把它添加到 /etc/backup.d 目录中就可以了。

如何解决物联网的规模和性能需求?

抛弃AOP!SpringBoot + YAML 零侵入数据脱敏神操作!

为什么不建议在 Docker 中跑 MySQL?

海信i630m手机的全面评测(颠覆性创新,体验未来智能手机)

全球物联网微控制器市场的三大关键见解

国产数据库生态工具大起底

面试官:如何提升项目并发性能?

友情链接

滇ICP备2023006006号-33