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

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

// fun.c
#include
void func()
{printf("编程珠玑\n");
}// main.c
#include
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
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被定义了两次,但是仍然可以编译通过,并且正常运行。这又是为什么呢?

符号

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

// symbol.c
#include
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的符号。

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

强符号与弱符号

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

__attribute__((weak))

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

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

#include
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.mzph.cn/news/338639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

当C语言函数执行成功时,返回1和返回0究竟哪个好?

基本上,没有人会将大段的C语言代码全部塞入 main() 函数。更好的做法是按照复用率高,耦合性低的原则,尽可能的将代码拆分不同的功能模块,并封装成函数。C语言代码的组合千变万化,因此函数的功能可能会比较复杂&#xf…

ubuntu自定义安装里怎么选_超市里的五香粉怎么选?看懂配料表,两个小技巧,不怕选不好。...

点击右上角【关注】,可获得本头条号推荐的更多美食干货五香粉是家庭常用的一种调味料,焖、炖、烧、煮、卤都常备使用,如今市场上的五香粉品牌也是相当繁多,质量也是参差不齐,有时买到的五香粉烧制的菜肴根本就不好吃&a…

原来C语言还可以这样实现“泛型编程”!

在回答标题问题之前,先了解下什么是泛型编程。泛型编程(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。C支持…

javadoc 标签_新的Javadoc标签@ apiNote,@ implSpec和@implNote

javadoc 标签如果您已经在使用Java 8,则可能会看到一些新的Javadoc标签: apiNote , implSpec和implNote 。 他们怎么了? 如果要使用它们,该怎么办? 总览 该帖子将快速查看标签的来源和当前状态。 然后&…

C语言入门基础之输入和输出

标准输入函数在stdio.h中scanf声明如下:/* Read formatted input from stdin.This function is a possible cancellation point and therefore notmarked with __THROW. */ extern int scanf (const char *__restrict __format, ...) __wur;使用Mac或Linux的同学&am…

camel java_与Java EE和Camel的轻量级集成

camel javaEnterprise Java具有不同的风格和观点。 从简单的平台技术(众所周知的Java EE)开始,到不同的框架和集成方面,最后是涉及以数据为中心的用户界面或特定可视化效果的用例。 Java EE本身无法解决的最突出的问题是“集成”。…

c语言 伪随机数程序,C语言的伪随机数

一直想好好的系统的学习一下C语言的伪随机数,今天终于逮到机会了伪随机数C语言中有可以产生随机数据的函数,需要添加stdlib.h和time.h头文件。首先在main函数开头加上srand(unsigned)time(NULL))。先来介绍一下srand头文件:定义函数&#xff…

最大隶属度原则_模糊数学笔记:六、模糊模型识别-I(最大隶属度原则)

1、模型识别的问题提出模型识别,通俗地理解即是对一个类别未知的对象进行归类(或者叫分类)。这里与聚类不同的是,聚类实际上是要区分出已有的样本哪些属于同一类,但并没有参考标准。而识别则事先有参考的标准&#xff…

C语言经典题

C 库函数 - tanh()描述C 库函数 double tanh(double x) 返回 x 的双曲正切。声明下面是 tanh() 函数的声明。double tanh(double x)参数x -- 浮点值。返回值该函数返回 x 的双曲正切。实例下面的实例演示了 tanh() 函数的用法。#include#include int main (){ double x, ret;…

C++11的模板改进

C11关于模板有一些细节的改进:模板的右尖括号模板的别名函数模板的默认模板参数模板的右尖括号C11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。int main() {std::vecto…

android 第三方圆弧进度条,android 可配置的圆弧进度条

Arc ProgressBar Configurable 圆弧环形进度条DEMOAttributesnameformatdescriptionborderWidthinteger圆弧边框的宽度progressStyletick/arc进度条类型,tick 为带刻度的radiusinteger半径arcbgColorcolor圆弧的边框背景degreeinteger弧度,设置为 0 即为…

C++定时器的实现之格式修订版

个人认为一个完备的定时器需要有如下功能:在某一时间点执行某一任务在某段时间后执行某一任务重复执行某一任务N次,任务间隔时间T那么如何实现定时器呢?下面是我自己实现的定时器逻辑,源码链接最后会附上。定时器中主要的数据结构…

java 性能调优_Java性能调优调查结果(第四部分)

java 性能调优这是本系列中的最后一篇文章,我们将分析我们在2014年10月进行的Java Performance Tuning Survey的结果。如果您尚未阅读第一篇文章,建议您首先阅读以下内容: 性能问题的频率和严重性 最受欢迎的监控解决方案 查找根本原因的工…

Android接入热敏打印机,Android 关于佳博和汉印蓝牙热敏打印机开发

接上篇文章Android之BLE(低功耗)蓝牙开发,本篇文章针对上篇博文中提出的两款打印机的开发流程进行记录。首先不管时佳博打印机还是汉印打印机,都是先对他们各自的lib进行导入,如图:导入lib之后,一定要记得进行sync pro…

C 桥接模式 - 开关和电器

桥接模式(Bridge Pattern)是将抽象部分与它的实现部分分离,使它们都可以独立地变化。1模式结构UML 结构图:Abstraction(抽象类):用于定义抽象类的接口,并且维护一个指向 Implementor…

javafx显示image_如何摆脱JavaFX中的重点突出显示

javafx显示image今天,有人问我是否知道摆脱JavaFX控件(分别是按钮)的焦点突出的方法: 有关此问题的大多数文章和提示建议添加: .button:focused {-fx-focus-color: transparent; }但是使用这种样式,仍然…

C语言没有引用,只有指针

这个问题是昨晚上有同学在知识星球提问,但是因为前两天一直在出差,比较累,没认真回答,今天打球回来,就把这个事情解决了。我想说的已经在题目说明的很清楚了,C语言是没有引用的,引用是在C 里面才…

C/C 代码规范注释有哪些讲究?

如果领导给你一个项目的源码让你阅读,并理解重构代码,但里面一句注释都没有,我想这肯定是之前同事“删库跑路”了。看一份源码什么很重要?除了各种代码规范之外,还有一个比较重要的就是注释。注释虽然写起来很痛苦, 但…

qq互联android sdk,qq互联.Android

导读:2.2调用示例,这里以发送文字微博接口的调用为例例,来说明通过requestAsync调用兼容接口的方法:,在上面的调用中,调用获取用户信息接口的示例代码如下:,调用发送带图微博接口的&…

C 流插入和流提取运算符的重载

<<运算符的重载C 在输出内容时&#xff0c;最常用的方式&#xff1a;std::cout << 1 <<"hello";提出问题&#xff1a;那这条语句为什么能成立呢&#xff1f;cout 是什么&#xff1f;为什么 << 运算符能用在 cout 上呢&#xff1f;原因&#…