前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >函数或全局变量重复定义时会怎样?

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

作者头像
编程珠玑
发布2021-03-03 14:41:58
1.6K0
发布2021-03-03 14:41:58
举报
文章被收录于专栏:编程珠玑编程珠玑编程珠玑

可能有些朋友第一反应是,那肯定是编译不过喽:

// 来源:公众号【编程珠玑】
// 作者:守望先生
// fun.c
#include<stdio.h>
void func()
{
    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 %d\n", num);
    change();
    printf("after:num is %d\n", 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++中)。

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

参考

参考书籍

  • 《深入理解计算机系统》
  • 《程序员的自我修养》
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 符号
  • 强符号与弱符号
  • 同名时,用哪个?
  • 总结
  • 参考
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档