首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

生成程序时出现奇怪的链接器错误:‘多重定义’`fnames';src/main.o:(.data.rel.local+0x0):在此首次定义‘

这个链接器错误“多重定义”通常发生在编译过程中,当同一个符号(在这个例子中是fnames)在多个源文件中被定义时。链接器无法确定应该使用哪个定义,因此会抛出错误。

基础概念

  • 符号:在编程中,符号可以是一个变量、函数、类等。
  • 定义:符号的具体实现或声明。
  • 多重定义:同一个符号在多个编译单元(通常是.o文件)中被定义。

原因

  1. 全局变量或函数在多个源文件中被定义
  2. 头文件中包含了变量或函数的定义,而不是声明

解决方法

  1. 使用extern关键字
    • 在头文件中声明变量或函数。
    • 在一个源文件中定义变量或函数。
    • 在一个源文件中定义变量或函数。
  • 使用static关键字
    • 如果变量或函数只在单个源文件中使用,可以将其声明为static,这样它的作用域就被限制在该文件内。
    • 如果变量或函数只在单个源文件中使用,可以将其声明为static,这样它的作用域就被限制在该文件内。
  • 使用匿名命名空间
    • 类似于static,但提供了更好的封装。
    • 类似于static,但提供了更好的封装。
  • 确保头文件只包含声明
    • 使用预处理器指令防止头文件被多次包含。
    • 使用预处理器指令防止头文件被多次包含。

示例代码

假设我们有两个源文件main.cpputils.cpp,以及一个头文件utils.h

代码语言:txt
复制
// utils.h
#ifndef UTILS_H
#define UTILS_H

extern int fnames;  // 声明

#endif // UTILS_H
代码语言:txt
复制
// utils.cpp
#include "utils.h"

int fnames = 0;  // 定义
代码语言:txt
复制
// main.cpp
#include "utils.h"
#include <iostream>

int main() {
    std::cout << "fnames: " << fnames << std::endl;
    return 0;
}

编译命令

确保使用正确的编译命令来链接所有目标文件:

代码语言:txt
复制
g++ main.cpp utils.cpp -o myprogram

通过上述方法,可以有效避免“多重定义”的链接器错误。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

6_Makefile与GCC

第六章 Makefile与GCC 6.1 交叉编译器 6.1.1 什么是交叉编译 ​ 简单地说,我们在PC机上编译程序时,这些程序是在PC机上运行的。我们想让一个程序在ARM板子上运行,怎么办? ​....S汇编语言源程序预处理、汇编.h预处理器文件通常不出现在命令行上 ​ 其他后缀名的文件被传递给连接器(linker),通常包括: ​ .o:目标文件(Object file,OBJ文件) ​ .a:归档库文件...​ 在写代码的时候,其实应该养成一个好的习惯就是任何的警告错误,我们都不要错过, ​ 编译错误必然是要解决的,因为会导致生成目标文件。...6.3 gcc编译器2_深入讲解链接过程 ​ 你会发现,可执行文件文件会比源代码大了。这是因为编译的最后一步链接会解析代码中的外部应用。然后将汇编生成的OBJ文件,系统库的OBJ文件,库文件链接起来。...为什么clean下的命令没有被执行?这是因为Makefile中定义的只执行命令的目标与工作目录下的实际文件出现名字冲突。

3.6K10

手把手教你写一个 Makefile 文件

对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。...链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以...总结一下,编译链接的过程如下: 源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。 在编译时,编译器只检测程序语法,和函数、变量是否被声明。...在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思是说,链接器未能找到函数的实现...【总结】:通过依赖(prerequisites)中的一些文件生成目标(target)文件,目标文件要按照命令(command)中定义的规则来生成。 2.

1.6K10
  • 【嵌入式】嵌入式项目手动与自动化构建流程详解

    .hex 文件:可选的 HEX 格式文件,同样用于烧录。 linker_script.ld:链接脚本,定义内存布局和段的分配等。用于告诉链接器如何安排程序的代码和数据在内存中的位置。...生成的文件: build/main.o build/drivers.o build/startup.o build/utils.o 2.5 链接阶段 链接阶段将所有目标文件 .o 链接成一个可执行文件...AS:指定汇编器为 arm-none-eabi-as。 LD:指定链接器为 arm-none-eabi-ld。 OBJCOPY:指定用于转换目标文件格式的工具,如生成二进制文件和 HEX 文件。...-k 选项:忽略错误并继续构建 make -k -k 选项在构建过程中遇到错误时,继续执行剩余的目标。这对于构建多个目标时检查不同的错误非常有帮助。...自动化操作:Makefile 中定义了链接规则,运行 make 时,自动调用链接器将所有目标文件链接成最终的可执行文件。

    13510

    Python中断多重循环的几种方法,你都知道吗?

    前言: 在编写Python程序时,我们经常会面临需要中断多重循环的情况。无论是在搜索特定条件满足的数据集合还是在处理嵌套循环时,灵活地中断循环是一项强大的技能。...那么,怎么才能跳出多重呢?在此记录备忘一下。 2.跳出多重循环 事实上,Python的标准语法是不支持跳出多重循环的,所以只能利用一些技巧,大概的思路有:写成函数、利用笛卡尔积、利用调试。...这个利用了调试模式中,只要出现报错就退出的原理,它伪装了一个错误出来。...协程和异步编程: 异步上下文管理器: 引入异步上下文管理器(async with语法),它使得在异步环境中使用上下文管理器更为方便。...深入异常处理: 自定义异常: 自定义异常有助于更好地组织异常层次结构,并为不同的错误情况提供更具体的异常类型。

    27610

    GCC 编译器的使用

    里面扩展了所有包含的文件、所有定义的宏。在编写程序时,有时候查找某个宏定义是非常繁琐的事,可以使用`-dM –E’选项来查看。...上面的 main.c 文件中,第 6 行定义的变量 i 没有被使用,但是使用“gcc –c –o main.o main.c”进行编译时并没有出现提示。...链接器处理归档文件的方法是:扫描归档文件,寻找某些成员,这些成员的符号目前已被引用,不过还没有被定义。但是,如果链接器找到普通的 OBJ 文件,而不是库文件,就把这个 OBJ 文件按平常方式链接进来。...+0xf): In function `sub_fun': : undefined reference to `printf' collect2: ld returned 1 exit status 出现了一大堆错误...(9)-u symbol 使链接器认为取消了 symbol 的符号定义,从而链接库模块以取得定义。可以使用多个 `-u’选项,各自跟上不同的符号,使得链接器调入附加的库模块。

    3.9K31

    实战Makefile前,该知道那些知识?

    make与make clean 生成目标文件规则(make命令): 执行make命令则会根据当前目录的Makefile文件定义的规则生成对应的目标文件。...例如mkdir命令,建立一个目录,如果目录不存 在,则mkdir不会出现错误。如果目录已存在,那么将产生错误。...系统自带变量: 系统自定义了一些变量,通常都是大学,比如CC,PWD,CLFAG等等,有些有默认值,有些没有,比如以下几种,如下 CPPFLAGS:预处理器需要的选项,如:-l CFLAGS:编译的时候使用的参数...clean: @rm $(OBJ) output 其中: main.o由main.c生成 add.o 由add.c生成 函数 Makefile提供了大量的函数,其中我们经常使用的函数主要有两个.../src/main.o 表示:把变量中所有后缀为.c的文件替换为.o。命令执行完,OBJ变量的值:./src/add.o ./src/main.o

    49120

    Linux环境下通过GDB调试C项目实战

    这个Makefile中的几条命令大致为: make clean:清除已经存在的result可执行文件 make/make result:将已经得到的可执行文件main.o与array.o链接成可执行文件...result,不开启O2优化或采用O0优化,在此之前将main.c和array.c分别编译成可执行文件main.o和array.o make_clean:清除已经存在的main.o可执行文件 array_clean...:清除已经存在的main.o可执行文件 array:清除已经存在的array.o可执行文件并编译array.c生成array.o文件 main:清除已经存在的main.o可执行文件并编译mian.c生成...,看起来让人放心,但是,仔细去调试它的array.c具体实现代码,就会发现其中函数调用时出现的数组越界,这样就会导致缓冲区泄露,可能会修改内存,造成不可知的错误,这样是最可怕的,因为无法准确预料到,后续会产生难以估计的错误...让人放心,但是,仔细去调试它的array.c具体实现代码,就会发现其中函数调用时出现的数组越界,这样就会导致缓冲区泄露,可能会修改内存,造成不可知的错误,这样是最可怕的,因为无法准确预料到,后续会产生难以估计的错误

    5.3K50

    CC++之makefile写法

    对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。...链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以...总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。...而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现...在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。

    99120

    makefile从入门到放弃——博主吐血整理的笔记

    f2.c -o f2.o# -Wall允许发出gcc所有有用的报警信息 f1.o:f1.c gcc -c -Wall f1.c -o f1.o#-c表示只编译不链接,生成目标文件“.o” main.o...创建和使用变量: 变量的类型 预定义变量: 变量名 变量含义 – AR 库文件维护程序名称,默认为ar.AS汇编程序名称,默认值为as。 – CC C编译器的名称,默认为cc。...通过运行C语言编译器来运行链接程序生成(一般是“ld”),其命令是:(CC) (LDFLAGS) n.o x : x.o y.o#并且x.c、y.c都存在时,隐含命令如下 cc -c x.c -o x.o...如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到指定的目录中去找寻文件了。 VPATH = src:.....makefile内容如下: CFLAGS=-c -Wall -I include test:src1/f1.o src2/f2.o main/main.o gcc src1/f1.o src2/f2.

    1.7K20

    c和fortran混编

    这个机制就是:不论是单一语言模块之间的 链接还是不同语言之间的混合链接,本质目的都是要链接器能找到定义于其他模块中的符号,如果全部找到,则链接成功,生成可执行的二进制文件。...这正是因为链接器(链接器其实 是ld,gcc调用了它)在foo.o中找到了main.o中需要的foo的定义,并且在main.o中找到了main的定义。...那么,说了这么多其实还是为了明确一点:要让链接器找到在一个文件中需要的符号定义,那么链接就能成功,就能生成可执行文件了。这也是混编的关键! ---- ---- 现在开始真真儿的了。...(其实,当fortran不为主程序时,可以不用链接libfrtbegin,起码这个小程序不用) 这里讨论了混编的基本原理,就是让链接器找到符号所在。从这点出发,一些混编问题都应该有了解决的思路。...任何东西,只需要在编译时告诉编译器你用了哪个动态链接库就可以了,如下: gcc -o out main.c libf1.so 这时候编译器有可能会报告如下错误: libf1.so: undefined

    1.6K41

    错误使用 C++ 模板特化产生的坑

    当编译器链接 .o 的时候,它会将 .o 中的符号全部链接进最终文件中,而当链接 .a 的时候,编译器则是会看当前链接结果是否存在未定义的符号,如果没有,那就不链接这个 .a 文件里面的内容。...在链接 .a 的时候,编译器发现我已经有 A::print() 了,不需要去链接 .a,因此就跳过了这个库,这就导致了最终输出的是编译器实例化出来的版本,而不是我们定义的特化版本。...问题虽然就这样解决了,但是刚刚的描述好像有点不对劲。我们说之前错误的写法会导致编译器自动实例化模板,而链接 .o 文件的时候,又会将 .o 中的符号链接进最终结果里,那这个时候怎么就没产生符号冲突呢?...如果两个都是强符号,那么就会出现冲突了。 那么,后续正确版本的 main.o 的符号又是怎样的呢?...另外,这顺便也能解释另一件事情:如果 main 依赖于 liba.a,而 liba.a 依赖于 libb.a,那么我们在链接库的时候就需要先链接 liba.a 再链接 libb.a,否则就会出现符号未定义的问题

    43730

    makefile终极奥义

    「prerequisites」 生成该target所依赖的文件和/或target 「command」 该target要执行的命令(任意的shell命令) 一个示例 首先还是使用上期「编译链接,你还不会用...OBJ:=main.o #定义变量 #引用变量 ${OBJ} #使用变量 $(OBJ) #推荐使用 除了自己定义的变量之外makefile还提供了预定义的变量 在隐含规则中的命令中,基本上都是使用了一些预先设置的变量...CXXFLAGS C++语言编译器参数。 CPPFLAGS C预处理器参数 LDFLAGS 链接器参数。(如:ld ) 隐晦规则 如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。...-f $(OBJ) $(TARGET) 在 rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事....#链接器参数 export CFLAGS LDFLAGS TOPDIR := $(shell pwd) export TOPDIR TARGET := app obj-y += main.o

    1.3K30

    跟我一起写 Makefile(一)

    关于程序的编译和链接 —————————— 在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件...链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以...总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。...而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现...在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。

    28610

    Makefile 的使用

    变量的导出(export): 在编译程序时,我们会不断地使用“make -C dir”切换到其他目录,执行其他目录里的 Makefile。...=定义或使用 define 指令定义的变量是延时变量;使用:=定义的变量是立即变量。需要注意的一点是,?=仅仅在变量还没有定义的情况下有效,即?=被用来定义第一次出现的延时变量。...开始时这两个文件还没有生成,在执行生成 test 的命令之前先将 main.o、sub.o 作为目标查找到合适的规则,以生成 main.o、sub.o。...④ 第 7、8 行就是用来生成 main.o、sub.o 的规则: 对于 main.o 这个规则就是: main.o:main.c gcc -c -o main.o main.c 对于 sub.o 这个规则就是...⑤ 第 5 行的命令在生成 main.o、sub.o 后得以执行。

    4.3K42

    Linux 开发 | 学习 Makefile

    隐式规则 Make 自己推导出来的规则,比如目标为 main.o 就推出依赖条件中需要 main.c和对应的编译命令 变量定义 类似程序中宏定义, 文本替换。...比如基本例子中最终目标是 edit,而 eidt 依赖 main.o...等文件链接而来,所以 make 就会自动去执行 main.o..等文件的生成规则,最后再合成 edit。...cc -o edit main.o command.o ... 第一行说明文件的依赖关系,edit 是由 main.o command.o... 这几个文件链接而成的,依赖于他们。...执行: $ gcc -MM mian.c 输出: main.o : main.c defs.h 因此,我们借助编译器帮我们自动生成依赖关系,并包含到 Makefile 中 -include $(DEPS...sed 替换输出文本,达到的目的类似如下 # 编译器输出格式 main.o : main.c defs.h # 转换为如下格式 main.o main.d : main.c defs.h 并保存到[.

    5.4K10

    Linux从入门到入土③(GCC、静态与动态链接库、Git、Make、Makefile)

    ]图片GCC编译流程使 用 gcc 进 行 的 编 译 过 程 是 一 个 相 对 复 杂 的 过 程 , 可 分 为以下四个阶段:预 处 理 ( Pre-Processing) 编译(Compiling...-fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。-shared参数的作用是告诉编译器生成一个动态链接库。...动态链接器动态链接器是一个独立于应用程序的进程,属于操作系统,当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L 指定的路径...,依次搜索,找到之后结束遍历,最终还是没找到,动态连接器就会提示动态库找不到的错误信息。...解决方案可执行程序生成之后,根据动态链接器的搜索路径,我们可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中

    1.6K10

    程序员C语言快速上手——工程篇(十三)

    ,通常会定义clean、install这些伪目标,install一般定义拷贝命令,将生成的可执行程序拷贝到应用安装目录下。...定义变量 源文件较多时,可以定义一个变量来保存,后续只需要引用该变量即可,如下,定义src_list来保存源文件列表,引用变量则使用${}包裹....定义变量使用set命令,取消命令可使用unset命令 # 定义变量 src_list set (src_list add.c sub.c mul.c div.c main.c) # 打印日志 message...,继续处理,但会跳过生成 FATAL_ERROR CMake错误,停止处理和生成 内置变量 在cmake中已经内置了一些变量,我们可以直接使用,也可使用set命令去修改 CMAKE_SOURCE_DIR...,因为link_directories命令传入相对路径时,会直接将相对路径传给编译器,导致出现找不到问题。

    3.1K30

    Makefile 的使用(在 Linux 中使用 make 命令来编译程序)

    变量的导出(export): 在编译程序时,我们会不断地使用“make -C dir”切换到其他目录,执行其他目录里的 Makefile。...=定义或使用 define 指令定义的变量是延时变量;使用:=定义的变量是立即变量。需要注意的一点是,?=仅仅在变量还没有定义的情况下有效,即?=被用来定义第一次出现的延时变量。...开始时这两个文件还没有生成,在执行生成 test 的命令之前先将 main.o、sub.o 作为目标查找到合适的规则,以生成 main.o、sub.o。...④ 第 7、8 行就是用来生成 main.o、sub.o 的规则: 对于 main.o 这个规则就是: main.o:main.c gcc -c -o main.o main.c 对于 sub.o 这个规则就是...⑤ 第 5 行的命令在生成 main.o、sub.o 后得以执行。

    9.1K10

    【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程

    连接 链接过程 : 使用 ld 连接器, 将 汇编 过程中生成的 ".o" 对象文件, 与其它 对象文件 和 库文件连接起来, 生成可执行的二进制文件; 连接示例 : 使用 gcc main.o 将汇编过程生成的对象文件...中有一个链接器将所有的对象文件链接到一起, 生成一个可执行文件; 解析对象文件 : 文件中存放的是机器码, 机器码中对其他文件中的 函数 或者 变量引用的地址没有解析, 当链接程序的时候才将这些地址写入...fuck 对象文件的链接次序 : 大部分编译器都可以随意排列顺序, 但是有的编译器需要注意链接次序; -- 编译器和连接器次序 : 编译器和链接器搜索外部函数 是 从左到右进行查找; -- 文件次序...: 调用函数的 对象文件, 该文件应该先于 定义函数的 对象文件, 这里 main.o 应该在 kill.o 之前; -- 错误排查 : 如果在编译程序的时候, 列出了所有的文件, 但是还出现了 未定义...错误, 就需要注意 文件排列的问题; 修改文件流程 : 当修改了一个文件之后, 只需要 重新编译这个文件即可, 之后将这个新编译的对象文件 与 原来的对象文件进行链接, 即可生成新的可执行文件; --

    69040

    objdump命令解析

    这不是必须的,objdump能自动识别许多格式,比如: objdump -b oasys -m vax -h fu.o 显示fu.o的头部摘要信息,明确指出该文件是Vax系统下用Oasys编译器生成的目标文件....symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。...然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。 .rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。...一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。....debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。

    4.7K21
    领券