首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Dev-C++ 编译报错看不懂?先搞清编译和链接是两回事

Dev-C++ 编译报错看不懂?先搞清编译和链接是两回事

原创
作者头像
PC电脑医生
发布于 2026-09-20 10:19:29
发布于 2026-09-20 10:19:29
1030
举报

写完代码,按下编译,底部弹出一行红字:

代码语言:bash
复制
collect2.exe: error: ld returned 1 exit status

这行字几乎没提供任何信息。 它没说是哪一行代码有问题,也没说缺了什么。

很多人对着它反复检查代码,怎么也找不出毛病。

问题在于:这行字不是"病因",是"结果"。 真正的错误信息在它上面几行。

这篇以 Bloodshed Dev-C%2B%2B 为例,讲清一次编译到底做了几件事——搞懂了,报错自然就能读懂。

编译不止一步

"编译"这个词,其实涵盖了四个阶段:

  • 预处理(做什么:展开 #include、处理 #define、条件编译;产物:.i 文件)
  • 编译(做什么:把 C/C%2B%2B 代码翻译成汇编;产物:.s 文件)
  • 汇编(做什么:把汇编翻译成机器码;产物:.o 目标文件)
  • 链接(做什么:把多个目标文件和库拼成可执行文件;产物:.exe)

平时按一次快捷键,这四步是一气呵成的。

但报错的来源不一样,处理方式也完全不同。

前三个阶段:语法层面

如果你的代码有语法错误——少了个分号、括号不配对、变量名打错——会在第二、三阶段就被拦下。

这类报错信息通常很明确:

代码语言:bash
复制
error: expected ';' before '}' token

直接告诉你在哪一行、缺了什么。 这种改起来不难。

第四个阶段:链接

问题就出在这一步。

链接器(linker)做的事,是把各个部分"拼"起来。

具体包括:

  1. 符号解析——你代码里调用了 printf,链接器得找到它的实现在哪
  2. 符号合并——把多个 .o 文件和库文件整合成一个完整的程序
  3. 地址分配——确定每个函数、变量最终放在内存的什么位置

如果它找不到某个东西,或者发现有冲突,就会中止。

collect2 到底是什么

回到开头那行报错。

这里有两个陌生的名字:

  • collect2:GCC 的一个链接器封装工具
  • ld:真正的链接器

流程是:gcc 调用 collect2,collect2 再去调用 ld。

报错信息拆开读:

  • ld returned 1 —— 链接器 ld 以非零状态码退出,说明它失败了
  • collect2: error —— 封装工具发现 ld 挂了,于是对外报错

所以这行字的意思是:"链接这一步失败了。"

它没说为什么失败——因为 collect2 自己也不清楚,它只是转发下游的状态。

真正的错误在上面

既然是转发,那"病因"必然在前面。

看到这行报错,第一件事是往上翻日志。

真正的错误通常是这几种。

一、未定义引用

最常见的一个:

代码语言:bash
复制
undefined reference to 'function_name'

意思是:你调用了这个函数,但链接器找不到它的实现。

常见原因:

  • 头文件包含了,但实现没写——尤其在多文件项目里,.h 里声明了函数,.cpp 里忘了定义
  • 函数名拼写不一致——C%2B%2B 区分大小写,MyFunc 和 myfunc 是两个不同的符号
  • 该链接的库没加上——用了某个库的函数,但没在项目里配置对应的 .a / .lib

这一类错误的排查重点是"声明与实现是否对得上"。

二、程序还在运行

这个原因挺隐蔽。

Windows 下,如果 .exe 正在运行,链接器无法覆盖它。

文件被占用,写入失败,链接自然中断。

表现:改了代码重新编译,突然就报链接错误,而之前编译得好好的。

处理:关掉那个还开着的程序窗口,或者去任务管理器里结束对应进程,再编译一次。

三、重复定义

代码语言:bash
复制
multiple definition of 'xxx'

同一个符号被定义了两次。

常见于:把函数的实现直接写在头文件里,然后这个头文件被多个源文件包含。

头文件里适合放声明(void func();),实现放到 .cpp 里。这是 C/C%2B%2B 项目组织的基本原则。

关于这个版本本身

说完报错,得说一个更根本的问题。

Bloodshed Dev-C%2B%2B 4.9.9.2 是二十年前发布的版本,早已停止开发。

它捆绑的编译器是 GCC 3.4.2——同样来自那个年代。

这意味着什么

GCC 3.4.2 只支持 C%2B%2B98 / C%2B%2B03 标准。

换句话说,2003 年之后新增的 C%2B%2B 语言特性,它基本都不认识:

  • auto 类型推导(引入标准:C%2B%2B11;这个版本:✗)
  • 范围 for 循环(引入标准:C%2B%2B11;这个版本:✗)
  • lambda 表达式(引入标准:C%2B%2B11;这个版本:✗)
  • nullptr(引入标准:C%2B%2B11;这个版本:✗)
  • 智能指针(引入标准:C%2B%2B11;这个版本:✗)
  • 结构化绑定(引入标准:C%2B%2B17;这个版本:✗)

这些不是"配置一下就能用"的问题——编译器不认识这些语法,报的会是语法错误。

后果是实际的:网上找到的教程、开源代码、学校发的参考资料,只要用了 C%2B%2B11 之后的写法,在这个环境里都编译不过。

这不是你写错了,是工具太老。

编码不匹配的问题

另一个常见困扰是中文乱码。

老版本默认使用 ANSI(在中文 Windows 上是 GBK) 编码,而现代开发环境普遍用 UTF-8。

同一个源文件,两种编码打开就是两种结果。

处理方式:用记事本打开源文件,「另存为」时把编码选成 ANSI,再用 Dev-C%2B%2B 打开。

原理是让文件的编码和 IDE 的默认解码方式对齐。 反过来,如果文件本来就是 ANSI,也可以把编辑器的编码设置改成对应值。

如果必须用这个版本

有些课程会指定用这个版本。真遇到编译不过,可以按这个顺序试。

第一步:往上翻日志找真正的错误

不要盯着 collect2 那行。 找它上方第一个 error:。

第二步:检查文件是否被占用

关掉正在运行的程序窗口,再编译。

第三步:确认代码没用新标准特性

如果用的是 C%2B%2B11 之后的写法,这个编译器就是不支持。

要么改写成老语法,要么换工具。

第四步:换个维护中的版本

如果课程没有强制要求具体版本号,用还在维护的分支会省很多事。

这类分支保留了相似的界面和操作习惯,但内置了较新的 GCC,支持 C%2B%2B14/17 甚至更高标准。

对学习者来说,这个差别的实际影响是:能不能跟上现在的教程和资料。

换个角度想

最后说一句实在的。

用一个编译器标准停在 2003 年的环境学 C%2B%2B,代价是持续的和现在的生态脱节。

教程里的代码跑不了,别人的项目编译不过,报错信息也对不上——这些时间本来可以用在学语言本身。

如果学校强制要求,那没办法。 但如果只是"习惯了这个界面",值得花点时间换一个还在维护的工具。

编程工具和语言标准一样,是会过时的。

Bloodshed Dev-C%2B%2B 在当年是个挺好用的入门工具,只是它的时代确实过去了。

安装包:

https://dubapkg.cmcmcdn.com/cs/257def/Bloodshed%20Dev-C%2B%2B%204.9.9.2.exe

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

目录
  • 编译不止一步
    • 前三个阶段:语法层面
    • 第四个阶段:链接
  • collect2 到底是什么
  • 真正的错误在上面
    • 一、未定义引用
    • 二、程序还在运行
    • 三、重复定义
  • 关于这个版本本身
    • 这意味着什么
    • 编码不匹配的问题
  • 如果必须用这个版本
    • 第一步:往上翻日志找真正的错误
    • 第二步:检查文件是否被占用
    • 第三步:确认代码没用新标准特性
    • 第四步:换个维护中的版本
  • 换个角度想
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档