c/c++中不同文件中的同名变量一定会redefine吗

今天讨论一个问题,那就是在 c/c++ 中,比如两个文件中的同名全局变量,一定会造成 redefine 的问题吗?

我们知道,在 c 和 c++ 中,编译器对符号的 mangling 是不同的,c 中通过下划线前缀加上符号的名称的方式,而 c++ 中符号 mangling 的规则要复杂很多,需要加上符号的类型,名称和长度,而函数中,有函数名,长度,参数名,长度和类型,返回值类型不作为 mangling 的元素。

所以在 c 中不能进行函数重载,而 c++ 中可以函数重载,且只需要参数个数,参数类型不同即可,而仅仅返回值不同不能构成函数重载。

那么如果在两个不同的文件中,有两个同名的全局变量,编译链接的时候会发生什么呢,一定会发生 redefine 的错误吗?

c 中的实验

实验环境:

  • OS:Ubuntu 16.04.7 LTS, xenial
  • Compiler: gcc 9.4
// 文件 a.c
#include <stdio.h>
int a;void foo() {a = 10;printf("%d, %p\n", a, &a);
}

这里使用了一个函数进行打印,是因为单纯写一个变量声明 int a;编译器优化时,会认为是一个 unused code,直接作为无用代码消除了。

// b.c
#include <stdio.h>
double a;void goo() {printf("%lf, %p\n", a, &a);
}

再添加一个 main.c

// main.c
#include <stdio.h>
extern void foo();
extern void goo();
int main(int argc, char *argv[])
{foo();goo();printf("HelloWorld\n");return 0;
}

写一个简单的 Makefile 来编译一下

SRC=$(shell ls *.c)
OBJS=$(subst .c,.o,${SRC})
$(info OBJS=$(OBJS))CFLAGS= -g -Wall -O0 #-fcommon%.o: %.cgcc $(CFLAGS) -c $< -o $@main: $(OBJS)gcc $^ -o $@

在 c 中,这段代码是可以编译的,且没有警告。编译完后,我们使用 readelf 来看下这几个 elf 文件。(readelf 是一个用于查看ELF格式文件信息的命令行工具)

# readelf a.o
Symbol table '.symtab' contains 17 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.c
......14: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM a15: 0000000000000000    45 FUNC    GLOBAL DEFAULT    1 foo16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

从上面可以发现,符号 a 的索引为 COM,表明这是一个 COMMON 的符号。printf 是 libc 中的 api,需要链接 libc.so,UND 表示 undefine。

再看下 b.o 同样如此

    14: 0000000000000008     8 OBJECT  GLOBAL DEFAULT  COM a15: 0000000000000000    39 FUNC    GLOBAL DEFAULT    1 goo16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

gcc 编译的 elf 文件中,符号是没有类型的,只能知道这是一个 GLOBAL 对象,OBJECT,符号类型是 COMMON。

无论是在 a.c 还是 b.c 中,符号 a 都是全局且未初始化的,gcc 编译器编译时将其编译成了 COMMON 符号。

什么是 COMMON 符号

from 《程序员的自我修养——链接、装载与库》, chapter 4.3 COMMON 块
早期的 Fortran 没有动态分配空间的机制,程序员必须事先声明它所需要的临时空间的大小。Fortran 把这种空间叫做 COMMON 块。当不同的目标文件需要的 COMMON 块空间大小不一致时,以最大的那块为准。

编译器在处理未初始化的全局变量时将其作为弱符号进行处理。而现在的链接机制就是在处理弱符号的时候,采用的与 COMMON 块一样的机制。

COMMON 符号针对的是一种弱符号处理。仅能包含在可重定位目标文件中,而不包含在可执行目标文件中。可执行目标文件,是链接器连接后生成的文件,这时,链接器会对符号进行处理,针对 COMMON 这种弱符号

  • 如果出现两个以上的同名强符号,会直接报 redefine 的错误
  • 如果有一个同名的是强符号,其他都是弱符号,那链接的结果以强符号的为准。但是如果弱符号 size 大小比强符号大,链接器会发出告警
  • 如果两个以上都是弱符号,链接结果以size最大的为准

编译器在编译成目标文件时,COMMON 所表示的弱符号因为没有确定最终所占空间的大小,此时无法在 BSS 段分配空间。只有在链接阶段确定了实际所占内存大小之后,才会在BSS段为其分配空间。最终 未初始化的全局变量就是保存在 BSS 段中的 。

总结

c 中 COMMON 符号其实就是一种弱符号。像上面的例子中的变量申明的情况,未初始化的全局变量,在单个编译单元中编译成目标文件时,就属于一种弱符号,编译器作为 COMMON 符号进行处理。这样最终在链接时,根据多个弱符号,去最大的为准的原则,最终以 double 作为 a 的 size 大小,保存到 BSS 段中。

c++ 中的实验

将上面的代码改成 cpp,使用 g++ 进行编译。可以查看一下 a.cpp.o

# readelf -sW a.cpp.o
Symbol table '.symtab' contains 12 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.cpp......9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 a10: 0000000000000000    45 FUNC    GLOBAL DEFAULT    1 _Z3foov11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

可以发现,foo 函数在 c++ 中的mangling与 c 中是有很大区别的。而符号 a 是一个全局强符号。在 c++ 中,认为未初始化的全局变量,默认初始化为 0,而不是像 c 中以弱符号的形式进行处理。所以,这种情况在 C++ 中编译链接时,会出现 redefine 的错误。

但是,如果将 a.cpp 中的 a 的声明加上 extern

extern int a;

这样结果就不一样了。可以看先这个时候 a.cpp.o 的符号

# readelf -sW a.cpp.o
Symbol table '.symtab' contains 17 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.cpp......14: 0000000000000000    45 FUNC    GLOBAL DEFAULT    1 _Z3foov15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND a16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

因为是 extern 的声明,此时 a 是一个 UND 的符号,链接器认为 a 的实际定义是在其他的模块中。这样就不会出现 redefine 的问题了。而 NOTYPE 表示在当前文件的符号表中没有特定类型与 a 关联。实际上,链接器本身也是不支持符号的类型的,变量类型对于链接器来说是透明的,它只需要知道符号的名字即可。

总结

c 中对未初始化的全局变量以弱符号进行处理,在单个编译单元中编译成目标文件时符号类型为 COMMON,这样多个弱符号链接时,链接器会选择 size 最大的那个同名符号的类型为最终改符号的类型。

而在 c++ 中,未初始化的全局变量默认是初始化为 0 的变量,保存在 BSS 段,不作为弱符号进行处理。而使用 extern 声明时,在编译单元中,会任务是一个 UND 的符号,在其他 translation unit (TU) 中进行了声明。

当出现两个及以上的同名强符号时,链接器就会发出 redefine 的告警。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/634942.shtml

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

相关文章

中移(苏州)软件技术有限公司面试问题与解答(1)—— 可信计算国密标准

接前一篇文章&#xff1a;中移&#xff08;苏州&#xff09;软件技术有限公司面试问题与解答&#xff08;0&#xff09;—— 面试感悟与问题记录 本文参考以下文章&#xff1a; 信息安全第五篇&#xff08;国密加密算法&#xff09;_domestic encryption algorithm-CSDN博客 …

音视频编解码学习记录

目录 学习资料个人git仓库 文章 学习资料 个人git仓库 标准,资料,笔记: https://gitee.com/fedorayang/video_and_audio_codec.git 文章 理解低延迟视频编码的正确姿势: https://cloud.tencent.com/developer/article/1358721

龙芯+RT-Thread+LVGL实战笔记(30)——电子琴演奏

【写在前面】正值期末,笔者工作繁忙,因此本系列教程的更新频率有所放缓,还望订阅本专栏的朋友理解,请勿催更。笔者在此也简要声明几点: 有些硬件模块笔者并没有,如LED点阵、压力传感模块、RFID模块等,因此这些模块的相关任务暂时无法给出经过验证的代码。其实,教程进行…

Webpack5入门到原理11:处理 js 资源

有人可能会问&#xff0c;js 资源 Webpack 不能已经处理了吗&#xff0c;为什么我们还要处理呢&#xff1f; 原因是 Webpack 对 js 处理是有限的&#xff0c;只能编译 js 中 ES 模块化语法&#xff0c;不能编译其他语法&#xff0c;导致 js 不能在 IE 等浏览器运行&#xff0c…

排序之归并排序

在计算机科学中&#xff0c;排序是一种常见的操作。它用于将一组元素按照一定的顺序排列。归并排序是一种非常有效的排序算法&#xff0c;其时间复杂度为O(nlogn)&#xff0c;空间复杂度为O(n)。本文将详细介绍归并排序的工作原理和实现方法。 归并排序的工作原理 归并排序的…

智慧之光:ChatGPT 引领工作效率新纪元

随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;已经逐渐融入我们的日常生活和工作中。其中&#xff0c;ChatGPT 作为一种先进的 AI 技术&#xff0c;正逐步改变我们的工作方式&#xff0c;提升我们的工作效率。本文灸哥将介绍如何利用ChatGPT提升工作效率&…

Pytest插件pytest-django让Django测试更高效

在Django应用开发中&#xff0c;测试是确保应用质量的关键环节。然而&#xff0c;Django自带的测试框架并非总能满足开发者的需求&#xff0c;而Pytest插件 pytest-django 则为我们提供了更为灵活、强大的测试工具。本文将深入介绍 pytest-django 插件的基本用法和实际案例&…

后面的输入框与前面的联动,输入框只能输入正数(不用正则)

概要 提示&#xff1a;这里可以描述概要 前面的输入框是发票金额&#xff0c;后面的输入框是累计发票金额&#xff08;含本次&#xff09;--含本次就代表后倾请求的接口的数据&#xff08;不是保存后返显的-因为保存后返显的是含本次&#xff09;是不含本次的所以在输入发票金…

Linux基础命令[1]-ls

文章目录 1. ls 命令说明2. ls 常用命令参数2.1 -l&#xff08;展示详细信息&#xff09;2.2 -a/-A&#xff08;展示隐藏文件&#xff09;2.3 -t/-c/-r&#xff08;展示信息排序&#xff09;2.4 -h&#xff08;展示文件大小&#xff09;2.5 -R&#xff08;递归展示&#xff09;…

iphone5s基带部分电源部分主主电源供电及

时序: 1.,基带电源的供电&#xff0c;基带电源也叫pmu。 首先时序图说电池提供供电&#xff0c;电池是J6接口&#xff0c;视频习惯把接口称之为座子。查U2_RF芯片&#xff0c;发现供电信号为PP_BATT_VCC_CONN&#xff0c;但是没查到跟电池座子有关系&#xff0c;电池座子写的是…

E. Increasing Subsequences -思维构造

题面 分析 如果构造一个递增序列&#xff0c;如 1 , 2 , 3 , 4 , . . . 1,2,3,4,... 1,2,3,4,... &#xff0c;可以发现是存在一定规律的&#xff0c;一一列举。 1 —— 有两个上升子序列&#xff08;1和空序列)。1,2 —— 有四个上升子序列&#xff08;空序列&#xff0c;…

linux C语言socket函数recv

recv 函数是在 Linux C 语言网络编程中用于从已连接的套接字接收数据的函数。它通常与 TCP 连接一起使用&#xff0c;但也可以用于 UDP&#xff08;尽管对于 UDP&#xff0c;更常使用 recvfrom&#xff0c;因为它还可以接收发送方的地址信息&#xff09;。 函数原型 recv 函数…

SpringMVC基础知识学习笔记

Universe Infinity Inc. 目录 一、学习SpringMVC主要是学什么1、SpringMVC的基本原理2、SpringMVC学习串联 二、快速体验SpringMVC的开发1、新建项目&#xff0c;转成web项目2、引入依赖3、编写Spring的配置类4、配置web启动类&#xff0c;替代web.xml5、编写Handler&#xff…

第十二篇【传奇开心果系列】Ant Design Mobile of React开发移动应用:内置组件实现酷炫CSS 动画

Ant Design Mobile of React 开发移动应用示例博文系列 第一篇【传奇开心果系列】Ant Design Mobile of React 开发移动应用:从helloworld开始 第二篇【传奇开心果系列】Ant Design Mobile of React 开发移动应用:天气应用 第三篇【传奇开心果系列】Ant Design Mobile of Reac…

Java 如何使用单例类

单例的目的是控制对象的创建&#xff0c;将对象的数量限制为只有一个。由于只有一个单例实例&#xff0c;单例的实例字段将只会在每个类中出现一次&#xff0c;就像静态字段一样。单例通常控制对资源的访问&#xff0c;比如数据库连接或套接字。 例如&#xff0c;如果您只有一…

beego的模块篇 - I18n国际化

1. i18n 安装导入 安装该模块&#xff1a; go get github.com/beego/i18n 导入引用包&#xff1a; import ("github.com/beego/i18n" ) conf 目录下就有 locale_en-US.ini 和 locale_zh-CN.ini 两个本地化文件。 本地化文件的文件名和后缀是随意的&#xff0c;不…

MySQL 索引(上)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL-进阶篇 &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现…

libpcap简明教程

文章目录 前言tcpdump的使用libpcap API的简单使用libpcap API的进阶使用 前言 因为之前简单写过wireshark入门指北&#xff0c;所以我知道基本的抓包流程。最近尝试调用libpcap的C API接口&#xff0c;顺道整理下。 本文实验如下&#xff1a; 使用tcpdump命令抓取目标地址为…

【漏洞复现】Hikvision SPON IP网络对讲广播系统命令执行漏洞(CVE-2023-6895)

文章目录 前言声明一、系统简介二、漏洞描述三、影响版本四、漏洞复现五、修复建议 前言 Hikvision Intercom Broadcasting System是中国海康威视&#xff08;Hikvision&#xff09;公司的一个对讲广播系统。 声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播…

[已解决]前端使用el-upload,后端使用文件上传阿里云报错:异常信息:java.lang.NullPointerException: null

前端使用el-upload&#xff0c;后端使用文件上传阿里云报错&#xff1a; 报错原因&#xff1a;前端image参数未传进去 解决方法&#xff1a;在el-upload添加属性 name"image" 文件传进去了&#xff01;