C++ 什么是未定义的引用/未解决的外部符号错误?如何修复它?

C++ 什么是未定义的引用/未解决的外部符号错误?如何修复它?,c++,linker-errors,undefined-reference,c++-faq,unresolved-external,visual-studio,C++,Linker Errors,Undefined Reference,C++ Faq,Unresolved External,Visual Studio,什么是未定义的引用/未解决的外部符号错误?什么是常见的原因和如何修复/阻止它们?< /P> < P>编译C++程序发生在几个步骤中,如2.2:< /P>所指定的。 翻译语法规则的优先顺序由以下阶段规定[见脚注] 物理源文件字符以实现定义的方式映射到基本源字符集 在以下情况下,为行尾指示器引入新行字符: 必需的[剪报] 删除紧跟着新行字符的反斜杠字符\的每个实例,将物理源行拼接到 形成逻辑源行。[剪报] 源文件被分解为预处理标记2.5和包含注释的空白字符序列。[剪报] 执行预处理指令,展开宏调用,

什么是未定义的引用/未解决的外部符号错误?什么是常见的原因和如何修复/阻止它们?< /P> < P>编译C++程序发生在几个步骤中,如2.2:< /P>所指定的。 翻译语法规则的优先顺序由以下阶段规定[见脚注]

物理源文件字符以实现定义的方式映射到基本源字符集 在以下情况下,为行尾指示器引入新行字符: 必需的[剪报] 删除紧跟着新行字符的反斜杠字符\的每个实例,将物理源行拼接到 形成逻辑源行。[剪报] 源文件被分解为预处理标记2.5和包含注释的空白字符序列。[剪报] 执行预处理指令,展开宏调用,并执行_Pragma一元运算符表达式。[剪报] 字符文字或字符串文字中的每个源字符集成员,以及每个转义序列和通用字符名 在字符文字或非原始字符串文字中,转换为 执行字符集的对应成员;[剪报] 相邻的字符串文字标记被连接起来。 分隔标记的空白字符不再有效。每个预处理令牌都转换为一个令牌。2.7. 这个 对生成的标记进行语法和语义分析并 翻译成一个翻译单位。[剪报] 翻译单元和实例化单元的组合如下:[SNIP] 解析所有外部实体引用。库组件链接以满足对中未定义的实体的外部引用 目前的翻译。所有此类翻译器输出都收集到 程序映像,其中包含在其 执行环境。重点矿山 [脚注]尽管在实践中不同的阶段可能会被折叠在一起,但实现的行为必须像这些单独的阶段一样

g++ -o test objectFile1.o objectFile2.o -lLibraryName
指定的错误发生在编译的最后阶段,通常称为链接。这基本上意味着您将一组实现文件编译成了目标文件或库,现在您想让它们一起工作

g++ -o test objectFile1.o objectFile2.o -lLibraryName
假设您在a.cpp中定义了符号a。现在,b.cpp声明了这个符号并使用了它。在链接之前,它只是假设该符号是在某个地方定义的,但它并不关心在哪里。链接阶段负责查找符号并将其正确链接到b.cpp,实际上是链接到使用它的对象或库

如果您使用的是Microsoft Visual Studio,您将看到项目生成.lib文件。其中包含导出符号表和导入符号表。导入的符号将根据链接所针对的库解析,导出的符号将提供给使用该.lib的库(如果有)

其他编译器/平台也有类似的机制

对于Microsoft Visual Studio,常见的错误消息有错误LNK2001、错误LNK1120、错误LNK2019,对于GCC,未定义对symbolName的引用

守则:

struct X
{
   virtual void foo();
};
struct Y : X
{
   void foo() {}
};
struct A
{
   virtual ~A() = 0;
};
struct B: A
{
   virtual ~B(){}
};
extern int x;
void foo();
int main()
{
   x = 0;
   foo();
   Y y;
   B b;
}
将使用GCC生成以下错误:

以及Microsoft Visual Studio中的类似错误:

常见原因包括:

未能链接到适当的库/对象文件或编译实现文件 通常,每个翻译单元将生成一个对象文件,其中包含在该翻译单元中定义的符号的定义。 要使用这些符号,必须针对这些对象文件进行链接

在gcc下,您可以指定要在命令行中链接在一起的所有对象文件,或者一起编译实现文件

g++ -o test objectFile1.o objectFile2.o -lLibraryName
这里的libraryName只是库的简单名称,没有特定于平台的添加。例如,在Linux上,库文件通常称为libfoo.So,但您只需编写-lfoo。在Windows上,相同的文件可能被称为foo.lib,但您将使用相同的参数。您可能必须使用-Lèdirectory›添加可在其中找到这些文件的目录。确保不要在-l或-l之后写空格

对于XCode:添加用户标题搜索路径->添加库搜索路径->将实际库引用拖放到项目文件夹中

在MSV下,添加到项目中的文件会自动将其对象文件链接在一起,并且会生成常用的lib文件。要在单独的项目中使用这些符号,您需要 需要在项目设置中包含lib文件。这是在项目属性的链接器部分的输入->附加依赖项中完成的。lib文件的路径应为 在链接器->常规->附加库目录中添加当使用随lib文件提供的第三方库时,如果不这样做,通常会导致错误

It c 还有一种情况是,您忘记将文件添加到编译中,在这种情况下,将不会生成对象文件。在gcc中,您需要将文件添加到命令行。在MSV中,将文件添加到项目将使其自动编译,尽管可以手动将文件从构建中单独排除

在Windows编程中,未链接必要库的指示符号是未解析符号的名称以_imp_2;开头。在文档中查找函数的名称,它应该说明您需要使用哪个库。例如,MSDN将信息放在名为Library的部分中每个函数底部的一个框中。

已声明但未定义变量或函数。 典型的变量声明是

extern int x;
因为这只是一个声明,所以需要一个单独的定义。相应的定义是:

int x;
例如,以下操作将生成错误:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition
类似的注释适用于函数。在未定义函数的情况下声明函数会导致以下错误:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition
A a;      //destructor undefined
请注意,您实现的函数与您声明的函数完全匹配。例如,您可能有不匹配的cv限定符:

void foo(int& x);
int main()
{
   int x;
   foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
                          //for void foo(int& x)
                          
其他不匹配的例子包括

函数/变量在一个命名空间中声明,在另一个命名空间中定义。 函数/变量声明为类成员,定义为全局,反之亦然。 函数返回类型、参数编号和类型以及调用约定并不完全一致。 来自编译器的错误消息通常会给出已声明但从未定义的变量或函数的完整声明。将其与您提供的定义进行仔细比较。确保每个细节都匹配。

班级成员: 纯虚拟析构函数需要实现。 与常规函数不同,声明析构函数纯仍然需要定义它:

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition
struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}
// graphics.lib    
#include "common_math.h" 
   
void draw(vec3 p) { ... } // vec3 comes from common_math.h
这是因为在隐式销毁对象时会调用基类析构函数,因此需要定义

虚拟方法必须实现或定义为纯方法。 这类似于没有定义的非虚拟方法,添加了如下推理: pure声明生成一个虚拟vtable,您可能会在不使用函数的情况下得到链接器错误:

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition
struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}
// graphics.lib    
#include "common_math.h" 
   
void draw(vec3 p) { ... } // vec3 comes from common_math.h
要使其工作,请将X::foo声明为纯:

非虚拟类成员 即使未明确使用,也需要定义某些成员:

struct A
{ 
    ~A();
};
以下内容将产生错误:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition
A a;      //destructor undefined
实现可以是内联的,在类定义本身中:

struct A
{ 
    ~A() {}
};
或外部:

A::~A() {}
如果实现在类定义之外,但在头中,则必须将方法标记为内联,以防止出现多个定义

如果使用,则需要定义所有使用的成员方法

一个常见的错误是忘记限定名称: 定义应该是

void A::foo() {}
静态数据成员必须在类之外的单个转换单元中定义: 可以为类定义中的整型或枚举型静态常量数据成员提供初始值设定项;但是,使用此成员的odr仍然需要如上所述的命名空间范围定义。C++11允许在类内为所有静态常量数据成员初始化。

模板实现不可见。 非专用模板的定义必须对使用它们的所有翻译单位可见。这意味着您不能分离模板的定义 到实现文件。如果您必须分离实现,通常的解决方法是在要分离的头的末尾包含一个impl文件 声明模板。常见的情况是:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
要解决这个问题,必须将X::foo的定义移动到头文件或使用它的翻译单元可见的某个位置

专门化模板可以在实现文件中实现,并且实现不必可见,但必须事先声明专门化

<>为进一步解释和另一种可能的解决方案,明确实例化参见.P/>符号在C程序中定义并用于C++代码。 <函数>或变量Vo.Fo在C程序中定义,并试图在C++程序中使用:

void foo();
int main()
{
    foo();
}

C++链接器希望名称被损坏,因此必须声明函数为:

extern "C" void foo();
int main()
{
    foo();
}

等效地,不是在C程序中定义,而是在C++中定义函数或变量空格FO,但用C连接定义:

extern "C" void foo();
<>你尝试在C++程序中使用C++。 如果整个库包含在头文件中,并编译为C代码;包括以下内容:

extern "C" {
    #include "cheader.h"
}
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}
template <typename T>
class Foo {
    template <typename T1>
    friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
    // ...
};
跨模块/dll编译器特定的错误导入/导出方法/类。 MSVS要求您使用u declspecdllexport和u declspecdllimport指定要导出和导入的符号

这种双重功能通常通过使用宏来实现:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
此_模块的宏只能在导出 作用这样,宣言:

DLLIMPEXP void foo();
扩展到

__declspec(dllexport) void foo();
并告诉编译器导出函数,因为当前模块包含其定义。当在不同的模块中包含声明时,它将扩展到

__declspec(dllimport) void foo();
并告诉编译器该定义位于链接所针对的某个库中(参见1)

您可以类似地导入/导出类:

class DLLIMPEXP X
{
};
如果所有其他操作都失败,请重新编译

我最近能够通过重新编译有问题的文件来消除Visual Studio 2012中未解决的外部错误。当我重新构建时,错误消失了

当两个或多个库具有循环依赖关系时,通常会发生这种情况。库A尝试使用B.lib中的符号,库B尝试使用A.lib中的符号。两者都不存在。当您尝试编译时,链接步骤将失败,因为它找不到B.lib。将生成A.lib,但不生成dll。然后编译B,它将成功并生成B.lib。重新编译A现在可以工作了,因为现在找到了B.lib。

对的未定义引用WinMain@16或类似的“不寻常”的主要入口点参考,尤其是对于

您可能没有根据实际的IDE选择正确的项目类型。IDE可能希望将例如Windows应用程序项目绑定到上述缺失参考中指定的入口点函数,而不是常用的int maiint argc、char**argv;签名

如果您的IDE支持普通控制台项目,您可能希望选择此项目类型,而不是windows应用程序项目

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}
下面是一个实际问题的详细信息和处理方法。

链接的.lib文件与.dll相关联

我也有同样的问题。假设我有项目MyProject和TestProject。我已经有效地将MyProject的lib文件链接到TestProject。但是,此lib文件是在构建MyProject的DLL时生成的。此外,我没有包含MyProject中所有方法的源代码,只包含对DLL入口点的访问


为了解决这个问题,我将MyProject构建为一个LIB,并将TestProject链接到这个.LIB文件,然后将生成的.LIB文件复制粘贴到TestProject文件夹中。然后,我可以将MyProject重新构建为DLL。它正在编译,因为TestProject链接到的lib确实包含MyProject类中所有方法的代码

此外,如果您使用的是第三方库,请确保您具有正确的32/64位二进制文件

指定相互依赖的链接库的顺序是错误的。 如果库相互依赖,则库的链接顺序确实很重要。通常,如果库A依赖于库B,那么在链接器标志中libA必须出现在libB之前

例如:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};
创建库:

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o
汇编:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
g++ -c  main.cpp foo.cpp gum.cpp
重复一遍,顺序很重要

Microsoft提供了一个pragma,用于在链接时引用正确的库

#pragma comment(lib, "libname.lib")
除了包含库目录的库路径外,这应该是库的全名。

什么是未定义的引用/未解析的外部符号

我将尝试解释什么是未定义的引用/未解析的外部符号

注意:我使用g++和Linux,所有示例都是针对它的

例如,我们有一些代码

// src1.cpp
void print();

static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;

int main()
{
    print();
    return 0;
}

生成对象文件

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
$ g++ src1.o src2.o -o prog
$ g++ src1.o src2.o -o prog
在汇编阶段之后,我们有一个对象文件,其中包含要导出的任何符号。 看看这些符号

$ readelf --symbols src1.o
  Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL14local_var_name # [1]
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_var_name     # [2]
我拒绝了输出中的一些行,因为它们无关紧要

因此,我们可以看到以下要导出的符号

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp没有导出任何内容,我们也没有看到它的符号

链接我们的对象文件

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
$ g++ src1.o src2.o -o prog
$ g++ src1.o src2.o -o prog
然后运行它

$ ./prog
123
$ ./prog 
123456789
链接器看到导出的符号并将其链接。现在,我们尝试在src2.cpp中取消注释行,如下所示

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
extern int local_var_name;

void print ()
{
    printf("%d%d\n", global_var_name, local_var_name);
}
并重新生成一个对象文件

$ g++ -c src2.cpp -o src2.o
好的,没有错误,因为我们只构建对象文件,链接还没有完成。 尝试链接

$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
之所以发生这种情况,是因为我们的本地变量名是静态的,即它对于其他模块不可见。 现在更深入。获取翻译阶段输出

$ g++ -S src1.cpp -o src1.s

// src1.s
look src1.s

    .file   "src1.cpp"
    .local  _ZL14local_var_name
    .comm   _ZL14local_var_name,4,4
    .globl  global_var_name
    .data
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; assembler code, not interesting for us
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits
所以,我们看到本地变量名没有标签,这就是为什么链接器没有找到它。但我们是黑客:我们可以修复它。在文本编辑器中打开src1.s并更改

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

i、 你应该像下面这样

    .file   "src1.cpp"
    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789
    .globl  global_var_name
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; ...
我们已经更改了local_var_name的可见性,并将其值设置为456789。 尝试从中生成一个对象文件

$ g++ -c src1.s -o src2.o
好的,请参阅readelf输出符号

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name
现在local_var_name已绑定GLOBAL为local

链接

然后运行它

$ ./prog
123
$ ./prog 
123456789
好的,我们破解它:

因此,当链接器在对象文件中找不到全局符号时,就会出现未定义的引用/未解析的外部符号错误。

编译器/IDE中出现错误 我最近遇到了这个问题,结果是。我不得不从项目中删除一个源文件,然后重新添加它以克服这个bug

如果您认为这可能是编译器/IDE中的错误,请执行以下步骤:

清理项目一些IDE可以选择这样做,您可以 所以 通过删除对象文件手动执行此操作 试着开始一个新项目, 从原始源代码复制所有源代码。 需要更新Visual Studio NuGet包以获得新的工具集版本

我在尝试将libpng与VisualStudio2013链接时遇到了这个问题。问题是包文件中只有VisualStudio2010和2012的库

正确的解决方案是希望开发人员发布一个更新的软件包,然后升级,但对我来说,它在VS2013中加入了一个额外的设置,指向VS2012库文件

通过查找packagename\build\native\packagename.targets并在该文件中复制所有v110部分,我在解决方案目录中的packages文件夹中编辑了该包。我在条件字段中将v110更改为v120,只是非常小心地将文件名路径全部保留为v110。这只是允许Visual Studio 2013链接到2012年的库,在本例中,它起了作用。

一个围绕GNU ld的包装器,不支持链接器脚本

一些.so文件实际上是,例如,文件是包含以下内容的ASCII文本文件:

INPUT (libtbb.so.2)
一些更复杂的构建可能不支持这一点。例如,如果在编译器选项中包含-v,则可以在要链接的库的详细输出列表中看到丢弃链接器脚本命令文件。一个简单的解决方法是将链接器脚本输入命令文件替换为该文件的副本或符号链接,例如

cp libtbb.so.2 libtbb.so

或者您可以将-l参数替换为.so的完整路径,例如,代替-ltbb do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2这是每个VC++程序员反复看到的最令人困惑的错误消息之一。让我们先把事情弄清楚

什么是符号? 简而言之,符号就是名称。它可以是一个变量名、函数名、类名、TyWIFF名称,或者除了C++语言中的那些名称和符号之外的任何东西。它是用户定义的或由依赖项库引入的,而依赖项库是另一个用户定义的

什么是外部的? 在VC++中,每个源文件.cpp、.c等都被视为一个翻译单元,编译器一次编译一个单元,并为当前翻译单元生成一个目标文件.obj。请注意,此源文件包含的每个头文件都将被预处理,并将被视为此翻译单元的一部分。翻译单元中的所有内容都被视为内部内容,其他所有内容都被视为外部内容。在C++中,可以使用诸如Extn、y*-DeScript、dLimPurt等关键字引用外部符号。 什么是“决心”? “解决”是一个链接时间术语。在链接时,链接器尝试为对象文件中无法在内部找到其定义的每个符号查找外部定义。此搜索过程的范围包括:

编译时生成的所有对象文件 显式或隐式 指定为此建筑应用程序的其他依赖项。 这个搜索过程称为解析

最后,为什么没有解决外部符号? 如果链接器找不到内部没有定义的符号的外部定义,则会报告未解析的外部符号错误

E.LNK2019的可能原因:未解决的外部符号错误。 我们已经知道,此错误是由于链接器未能找到外部符号的定义,可能的原因可分为:

定义存在 例如,如果我们在a.cpp中定义了一个名为foo的函数:

int foo()
{
    return 0;
}
在b.cpp中,我们希望调用函数foo,因此我们添加了

void foo();
要声明函数foo并在另一个函数体中调用它,请说bar:

现在,当您构建这段代码时,您将得到一个LNK2019错误,抱怨foo是一个未解析的符号。在本例中,我们知道foo在a.cpp中有它的定义,但与我们称之为不同返回值的定义不同。这就是定义存在的情况

定义不存在 如果我们想调用库中的某些函数,但导入库未从以下位置添加到附加依赖项列表集中:项目|属性|配置属性|链接器|输入|项目设置的附加依赖项。现在链接器将报告LNK2019,因为当前搜索范围中不存在该定义。

使用链接器帮助诊断错误 大多数现代的链接器都有一个详细的选项,可以不同程度地打印出来

链接调用命令行, 链接阶段包含哪些库的数据, 图书馆的位置, 使用的搜索路径。 对于gcc和clang;您通常会在命令行中添加-v-Wl、-verbose或-v-Wl、-v。更多细节可在这里找到

Linux。 LLVM。 GCC简介。 对于MSVC,将/VERBOSE特别是/VERBOSE:LIB添加到链接命令行

上的MSDN页。
假设你有一个C++项目,它有一个 一千个.cpp文件和一千个.h文件。假设该项目还依赖于十个静态库。假设我们在Windows上,我们在VisualStudio20xx中构建我们的项目。当您按Ctrl+F7 Visual Studio开始编译整个解决方案时,假设解决方案中只有一个项目

编辑的意义是什么

Visual Studio搜索文件.vcxproj并开始编译扩展名为.cpp的每个文件。编译顺序未定义。因此,您不能假定文件main.cpp是先编译的 如果.cpp文件依赖其他.h文件来查找符号 可以在文件.cpp中定义,也可以不定义 如果存在一个.cpp文件,编译器在其中找不到一个符号,则编译器时间错误将引发消息“找不到符号x” 对于扩展名为.cpp的每个文件,都会生成一个对象文件.o,Visual Studio还会将输出写入名为ProjectName.cpp.Clean.txt的文件中,该文件包含链接器必须处理的所有对象文件。 编译的第二步由链接器完成。链接器应该合并所有的目标文件,并最终生成输出,输出可以是可执行文件或库

链接项目的步骤

解析所有对象文件并找到仅在标题中声明的定义,例如:在前面的回答中提到的类的一个方法的代码,或者是类中成员的静态变量的初始化事件 如果在目标文件中找不到一个符号,则还会在其他库中搜索该符号。要将新库添加到项目,请选择“配置属性”->“VC++目录”->“库目录”,然后在此处指定用于搜索库的其他文件夹,并选择“配置属性”->“链接器”->“输入”指定该符号的名称图书馆 -如果链接器找不到您在一个.cpp中写入的符号,他会引发一个链接器时间错误,听起来像 错误LNK2001:未解析的外部符号无效\uuuu cdecl foovoid?foo@@YAXXZ 观察

一旦链接器找到一个符号,他就不会在其他库中搜索它 链接库的顺序确实很重要。 如果链接器在一个静态库中找到一个外部符号,他会在项目的输出中包含该符号。但是,如果该库是动态共享的,他不会在输出中包含代码符号,但可能会发生运行时崩溃 如何解决这种错误

编译器时间错误:

确保你的C++项目语法正确。 链接器时间错误

定义在头文件中声明的所有符号 使用pragma一次,允许编译器不包含一个头,如果它已经包含在当前编译的.cpp中 确保外部库中不包含可能与头文件中定义的其他符号冲突的符号 使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例化生成适当的代码。
由于人们似乎被引导到这个问题,当涉及到链接器错误时,我将在这里添加这个

GCC 5.2.0中出现链接器错误的一个可能原因是默认情况下选择了新的libstdc++库ABI

如果出现链接器错误,即未定义对涉及std::\u cxx11命名空间或标记[abi:cxx11]中类型的符号的引用,则可能表示您正在尝试将使用不同值为_GLIBCXX\u USE\u cxx11\u abi宏编译的对象文件链接在一起。这通常发生在链接到使用较旧版本的GCC编译的第三方库时。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码

因此,如果您在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件需要检查的事情。

清理并重建 对构建的清理可以清除以前构建、失败构建、不完整构建和其他构建系统相关构建问题遗留下来的死木头

通常,IDE或构建将包括某种形式的清除功能,但这可能没有正确配置(例如在手动生成文件中),或者可能会失败(例如,中间或生成的二进制文件是只读的)

清理完成后,验证清理是否成功,以及是否已成功删除所有生成的中间文件,例如自动生成的文件

这一过程可以看作是最后的手段,但往往是良好的第一步;特别是如果与错误相关的代码最近已在本地或从源存储库添加。

交友模板。。。 给定带有友元运算符或函数的模板类型的代码段

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
由于它没有实现,链接器无法找到它并导致错误

要更正此问题,可以声明一个t 在Foo类型之前使用emplate运算符,然后将适当的实例化声明为友元。语法有点笨拙,但如下所示:

extern "C" {
    #include "cheader.h"
}
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}
template <typename T>
class Foo {
    template <typename T1>
    friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
    // ...
};
或者,运算符一致性UNICODE定义的实现 Windows UNICODE构建是使用TCHAR等定义为wchar\u t等进行构建的。当不使用UNICODE定义为构建时,使用TCHAR等定义为char等。这些UNICODE和_UNICODE定义影响所有;LPTSTR、LPCTSTR和它们的麋鹿

构建一个定义了UNICODE的库,并尝试在未定义UNICODE的项目中链接它,将导致链接器错误,因为TCHAR的定义不匹配;char vs.wchar\t

错误通常包括一个带有char或wchar_t派生类型的函数a值,这些函数还可能包括std::basic_字符串等。浏览代码中受影响的函数时,通常会引用TCHAR或std::basic_字符串等。这表明代码最初用于UNICODE和多字节字符或窄构建

要纠正此问题,请使用统一的UNICODE和_UNICODE定义构建所有必需的库和项目

这可以通过以下两种方式实现:

#define UNICODE
#define _UNICODE
或在项目设置中

项目属性>常规>项目默认值>字符集

或者在命令行上

/DUNICODE /D_UNICODE
如果不打算使用UNICODE,请确保未设置定义,和/或在项目中使用多字符设置并一致应用,此选项也适用

不要忘记在发布版本和调试版本之间保持一致。

当包含路径不同时 当头文件及其关联的共享库.lib文件不同步时,可能会发生链接器错误。让我解释一下

链接器是如何工作的?链接器通过比较头中声明的函数声明的签名,将其与共享库中的定义相匹配。如果链接器找不到完全匹配的函数定义,则可能会出现链接器错误

即使声明和定义似乎匹配,是否仍可能获得链接器错误?对它们在源代码中可能看起来相同,但这实际上取决于编译器看到的内容。基本上,你可能会遇到这样的情况:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
请注意,尽管两个函数声明在源代码中看起来完全相同,但根据编译器的不同,它们实际上是不同的

你可能会问,一个人怎么会陷入这样的境地?当然包括路径!如果在编译共享库时,include路径指向header1.h,而您最终在自己的程序中使用了header2.h,那么您将不得不抓挠头,想知道发生了什么

下面将举例说明这在现实世界中是如何发生的

以实例作进一步阐述 我有两个项目:graphics.lib和main.exe。这两个项目都依赖于公共数学。假设库导出以下函数:

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition
struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}
// graphics.lib    
#include "common_math.h" 
   
void draw(vec3 p) { ... } // vec3 comes from common_math.h
然后你继续把这个库包含在你自己的项目中

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}
轰!你得到一个链接器错误,你不知道为什么失败。原因是公共库使用相同的include common_math.h的不同版本。在本例中,我通过包含不同的路径来说明这一点,但它可能并不总是那么明显。可能编译器设置中的包含路径不同

注意,在本例中,链接器会告诉您它找不到draw,而实际上您知道它显然是由库导出的。你可能会花几个小时挠头,想知道出了什么问题。问题是,链接器会看到不同的签名,因为参数类型略有不同。在本例中,就编译器而言,vec3在这两个项目中是不同的类型。这可能是因为它们来自两个稍有不同的包含文件,可能包含文件来自库的两个不同版本

调试链接器 如果您使用的是Visual Studio,则DUMPBIN是您的朋友。我相信其他编译器也有类似的工具

过程如下:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
请注意链接器错误中给出的奇怪的损坏名称。如。draw@graphics@XYZ。 将库中导出的符号转储到文本文件中。 搜索导出的感兴趣的符号,注意被损坏的名称是不同的。 请注意为什么被弄坏的名字最终会不一样。您将能够看到参数类型是不同的,即使它们在源代码中看起来相同。 他们不同的原因。在上面给出的示例中,它们是不同的,因为包含文件不同。 [1] 我所说的项目是指一组链接在一起以生成库或可执行文件的源文件

编辑1:重写第一节以便于理解。请在下面发表评论,让我知道是否还有其他需要 待修复。谢谢

链接在引用库的对象文件之前先使用库 您正试图用GCC工具链编译并链接您的程序。 您的链接指定了所有必要的库和库搜索路径 如果libfoo依赖于libbar,那么您的链接会正确地将libfoo放在libbar之前。 由于未定义对某个错误的引用,链接失败。 但是所有未定义的something都在您拥有的头文件中声明 在您链接的库中包含和实际上定义了。 例子是C。它们也可以是C++

一个涉及您自己构建的静态库的最小示例 我的_lib.c

表1.c

您可以编译您的程序:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -c -o eg2.o eg2.c
您尝试将其与libmy_lib.a链接,但失败:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
如果在一个步骤中编译和链接,则会得到相同的结果,如:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
一个涉及共享系统库的最小示例,压缩库libz 表2.c

尝试将您的程序与libz链接,但失败:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
如果一次性编译和链接,则相同:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
示例2的一个变体涉及pkg config:

你做错了什么? 按照要链接的对象文件和库的顺序,使 程序中,将库放置在引用的对象文件之前 他们您需要将库放在引用的对象文件之后 对他们来说

正确链接示例1:

$ gcc -o eg1 eg1.o -L. -lmy_lib
$ gcc -o eg2 eg2.o -lz
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8
成功:

$ ./eg1 
Hello World
$ ./eg2 
1.2.8
正确链接示例2:

$ gcc -o eg1 eg1.o -L. -lmy_lib
$ gcc -o eg2 eg2.o -lz
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8
成功:

$ ./eg1 
Hello World
$ ./eg2 
1.2.8
正确链接示例2 pkg配置变量:

$ gcc -o eg1 eg1.o -L. -lmy_lib
$ gcc -o eg2 eg2.o -lz
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8
解释 从现在开始,阅读是可选的

默认情况下,GCC在发行版上生成的链接命令, 在中从左到右使用链接中的文件 命令行序列。当它发现一个文件引用某个东西时 并且不包含它的定义,to将搜索定义 在更右边的文件中。如果它最终找到了一个定义 参考已解决。如果最终仍有任何引用未解决, 链接失败:链接器不向后搜索

首先,示例1,使用静态库my_lib.a

静态库是对象文件的索引存档。当链接器 在链接序列中查找-lmy_lib,并确定它引用了 到静态库。/libmy_lib.a,它想知道您的程序 需要libmy_lib.a中的任何对象文件

libmy_lib.a中只有一个对象文件,即my_lib.o,并且只定义了一个东西 在我的_lib.o中,即函数hw

链接器将决定您的程序需要my_lib.o,当且仅当它已经知道这一点时 您的程序在已经存在的一个或多个目标文件中引用了hw 已添加到程序中,并且没有已添加的任何对象文件 包含硬件的定义

如果这是真的,那么链接器将从库中提取my_lib.o的副本并 将其添加到您的程序中。然后,您的程序包含hw的定义,所以 其对硬件的引用已解决

当您尝试链接程序时,如:

$ gcc -o eg1 -L. -lmy_lib eg1.o
gcc -o eg2 -lz eg2.o
链接器在看到时未将eg1.o添加到程序中 -我爱你。因为在这一点上,它还没有看到eg1.o。 您的程序尚未对hw:it进行任何引用 根本不做任何引用,因为它做的所有引用 都在eg1.o中

因此链接器不会将my_lib.o添加到程序中,并且没有进一步的 用于libmy_lib.a

接下来,它找到eg1.o,并将其添加到程序中。中的对象文件 链接序列始终添加到程序中。现在,该程序使 对硬件的引用,不包含硬件的定义;但是 连杆序列中没有任何东西可以提供缺失的连杆 释义对hw的引用最终无法解析,链接失败

第二,示例2,使用共享库libz

共享库不是对象文件或类似文件的存档。它是 更像是一个没有主函数和 而是公开它定义的多个其他符号,以便 程序可以在运行时使用它们

如今,许多Linux发行版都配置了GCC工具链,以便其语言驱动程序GCC、g++、gfortran等 指示系统链接器ld根据需要链接共享库。 你有一个发行版

这意味着,当链接器在链接序列中找到-lz,并指出它引用了 对于共享库,比如/usr/lib/x86_64-linux-gnu/libz.so,它想知道它添加到程序中的任何尚未定义的引用是否具有libz导出的定义

如果这是真的,那么链接器将不会从libz和libz中复制任何块 将它们添加到您的程序中;相反,它只会修改程序的代码 以便:-

在运行时,系统程序加载器将把libz的副本加载到 无论何时加载程序的副本,都要执行与程序相同的过程来运行它

在运行时,只要程序引用som 中定义的ething libz,该引用使用中libz副本导出的定义 同样的过程

你的程序只想引用一个定义由libz导出的东西, 即函数zlibVersion,在eg2.c中只引用一次。 如果链接器将该引用添加到程序中,然后找到定义 由libz导出时,将解析引用

但当您尝试链接程序时,如:

$ gcc -o eg1 -L. -lmy_lib eg1.o
gcc -o eg2 -lz eg2.o
事件的顺序是错误的,与示例1中的顺序相同。 当链接器找到-lz时,没有任何引用 在程序中:它们都在eg2.o中,这还没有被看到。所以 链接器决定它不使用libz。当它到达eg2.o时,将其添加到程序中, 然后对zlibVersion进行未定义的引用,完成联动顺序; 该引用未解析,链接失败

最后,示例2的pkg配置变体现在有了一个明显的解释。 外壳扩展后:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o
变成:

gcc -o eg2 -lz eg2.o
这又是一个例子2

我可以重现示例1中的问题,但不能重现示例2中的问题 联系:

gcc -o eg2 -lz eg2.o
对你来说很好

或者:这种链接在Fedora23上运行良好,但在Ubuntu 16.04上失败

这是因为链接工作的发行版是 不将其GCC工具链配置为根据需要链接共享库

回到过去,类unix系统将静态和共享链接起来是很正常的 图书馆有不同的规则。链接序列中的静态库已链接 在示例1中解释的按需基础上,但共享库是无条件链接的

这种行为在链接时是经济的,因为链接者不必考虑 程序是否需要共享库:如果是共享库, 链接它。大多数链接中的大多数库都是共享库。但也有缺点:-

在运行时,这是不经济的,因为它会导致共享库被删除 与程序一起加载,即使不需要它们

静态库和共享库的不同链接规则可能会混淆 对于不熟练的程序员,他们可能不知道在他们的链接中是否有-lfoo 将解析为/some/where/libfoo.a或/some/where/libfoo.so, 并且可能不理解共享库和静态库之间的区别 无论如何

这种权衡导致了今天的分裂局面。有些发行版已经发行了 更改了共享库的GCC链接规则,以便 这一原则适用于所有图书馆。一些发行版坚持使用旧版本 对

为什么即使我同时编译和链接,我仍然会遇到这个问题? 如果我这样做:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
当然,gcc必须首先编译eg1.c,然后链接结果 带有libmy_lib.a的对象文件。那么它怎么可能不知道那个对象文件呢 在进行链接时是否需要

因为使用单个命令编译和链接不会更改 连杆顺序的顺序

当您运行上面的命令时,gcc会发现您需要编译+ 联系。因此,在幕后,它生成一个编译命令,并运行 然后,它生成一个链接命令,并运行它,就像运行 两个命令:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
因此,链接会失败,就像运行这两个命令一样。这个 您在失败中注意到的唯一区别是gcc生成了一个 compile+link案例中的临时对象文件,因为您没有告诉它 使用eg1.o。我们看到:

而不是:

eg1.o: In function `main':
另见 将相互依赖的库按错误的顺序排列只是一种方法 在其中,您可以获得需要对即将到来的事件进行定义的文件 在链接中位于提供定义的文件之后。把图书馆放在第一位 引用它们的对象文件是另一种方法,同样的错误。

在const变量声明/定义中只删除外部的C++。 对于C的人来说,在C++全局变量中有内部或静态的连接可能是一个惊喜。在C语言中,情况并非如此,因为所有全局变量都是隐式外部变量,即当缺少static关键字时

例如:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}
正确的做法是使用头文件并将其包含在file2.cpp和file1.cpp中

extern const int test;
extern int test2;

或者,可以在file1.cpp中使用显式extern声明const变量

,即使这是一个有多个可接受答案的非常老的问题,我想与大家分享如何解决一个模糊的未定义错误引用

图书馆的不同版本 我使用别名来表示std::filesystem::path:filesystem自C++17以来就在标准库中,但我的程序也需要在C++14中编译,因此我决定使用变量别名:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
假设我有三个文件:main.cpp、file.h、file.cpp:

文件.h包含并包含上述代码 file.cpp,file.h的实现,包括的file.h main.cpp包括的和文件.h 注意差别 main.cpp和file.h中使用的不同库。因为main.cpp包含了.h之后的文件,所以那里使用的文件系统版本是C++17版本。我用以下命令编译程序:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
$g++-g-std=c++17-c main.cpp->将main.cpp编译为main.o $g++-g-std=c++17-c file.cpp->将file.cpp和file.h编译为file.o $g++-g-std=c++17-o可执行main.o file.o-lstdc++fs->链接main.o和file.o

这样,file.o中包含并在main.o中使用的任何需要path_t的函数都会给出未定义的引用错误,因为main.o引用的是std::filesystem::path,而file.o引用的是std::experimental::filesystem::path

决议 要解决这个问题,我只需要将文件.h中的更改为。

在链接共享库时,确保使用的符号不被隐藏。 gcc的默认行为是所有符号都可见。但是,当使用选项-fvisibility=hidden构建转换单元时,在生成的共享对象中,只有标有_属性_u可见性默认值的函数/符号是外部的

您可以通过调用以下命令来检查要查找的符号是否为外部符号:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 
隐藏/本地符号由nm以小写符号类型显示,例如,对于代码段,t而不是't:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

也可以使用NM和选项-C来删除名字,如果使用C++。 与Windows DLL类似,可以使用define标记公共函数,例如,将DLL_public定义为:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}
大致对应于Windows的/MSVC版本:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif
更多信息可以在gcc wiki上找到

当使用-fvisibility=hidden编译翻译单元时,生成的符号仍然具有外部链接,以大写符号类型nm显示,并且如果目标文件成为静态库的一部分,则可以毫无问题地用于外部链接。仅当对象文件链接到共享库中时,链接才成为本地链接

要查找对象文件中隐藏的符号,请运行:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
函数或类方法在源文件中用内联说明符定义。 例如:-

main.cpp

foo.h1

口香糖h 1

foo.cpp 1

链接:

如果编译器可以在调用gum的每个源文件中看到gum的定义,则只能将gum定义为内联。这意味着它的内联定义需要存在于包含在每个源文件中的头文件中 你可以在其中编译gum。做两件事中的一件:

或者不要内联定义

从源文件定义中删除内联说明符:

foo.cpp 2

成功

或正确内联

头文件中的内联定义:

foo.h2


完整地说,这个答案应该提到GCC的可见性和Windows“.def”文件,因为它们也会影响符号名称和存在。@rubenvb我很久没有使用过.def文件了。请随意添加答案或编辑此答案。正确-当库具有循环依赖关系时会发生这种情况。我扩展了您的答案并在主答案中进行了链接。谢谢。就个人而言,我认为MS linker错误消息和GCC错误一样可读。它们还有一个优点,即为未解析的外部对象包含损坏和未混合的名称。当您需要直接查看库或对象文件以了解问题所在(例如,调用约定不匹配)时,使用损坏的名称会很有帮助。此外,我不确定是什么版本的MSVC在这里产生了错误,但较新的版本包含了引用未解析外部符号的函数的名称,包括“已损坏”和“未混合”。David Drysdale写了一篇关于链接器如何工作的伟大文章:。考虑到这个问题的主题,我认为它可能会很有用。@TankorSmash是否使用gcc?更准确地说,@luchian如果您添加正确的,修复上述错误,那就太好了。我会在列表中添加一个常见的情况-只是不将文件添加到其中使用的MSVC项目中:@LuchianGrigore“请随意添加答案”我更愿意在您的主要答案中添加相关链接,如果您希望允许.@jave.web:在这种情况发生时,程序员通常会注意到他没有这个指针,也没有访问类成员的权限。完成编译是非常罕见的,只有在非静态成员函数缺少其限定名时,链接才会失败。@AlbertvanderHorst这个问题太笼统了,不能接受有用的答案。下面的答案是否有用?它们可能有用,正如许多问题的答案被标记为过于笼统一样,老实说,我希望看到我们对大多数新用户提出的要求是最低限度的重复性示例。我的意思不是什么,只是-我们不能期望人们遵守我们自己不知道的规则。我不得不指出,这通常是因为没有主函数而不是没有WinMain。有效的C++程序需要一个主程序。

标题中的d可以在Just Think中找到,您可能希望强调这两种方法都是可能的,而dtor实际上并不是一个例外。乍一看,你的措辞并不明显。这似乎过于具体——也许一个新的线程会是这个答案的更好的地方。@LuchianGrigore:我确实想发帖,因为那个问题正是这个问题,但它被标记为这个问题的副本,所以我无法在那里回答它。所以我把我的答案贴在这里了。这个问题已经有了公认的答案。它被标记为重复,因为上面列出了一般原因。如果我们在这里对未包含的库的每个问题都有一个答案,会发生什么?@LuchianGrigore:这个问题不是特定于库的,它会影响使用Visual Studio包管理系统的所有库。我碰巧发现了另一个问题,因为我们都有libpng的问题。对于libxml2、libiconv和glew的相同解决方案,我也遇到了同样的问题。这个问题是关于VisualStudio的包管理系统的问题,我的回答解释了原因并提供了解决方法。有人只是看到了未解决的外部问题,并认为这是一个标准的链接器问题,而实际上是一个包管理问题。或者相反,如果您开发一个C库,一个很好的规则是通过使用ifdef u cplusplus[\n]externC{[\n]endif和ifdef u cplusplus[\n]}[\n]endif包围所有导出的声明来保护头文件[n]是真正的回车,但我不能正确地写在评论中。正如上面的评论,“创建混合语言标题”部分帮助:真的帮助了我!这是我的情况,当我寻找这个问题时,如果你把你的C++ C++头文件意外地包含在外部C:{include}。你的答案不是针对visual studio的吗?这个问题没有指定任何IDE/编译器工具,因此你的答案对于非visual studio部分来说毫无用处。你是对的。但是每个IDE编译/链接过程中的每一个都有一点不同。但是文件的处理方式完全相同,即使g++在运行时也会执行相同的操作标记。问题实际上不是关于IDE,而是关于链接问题的答案。链接问题与IDE无关,而是与编译器和构建过程有关。是的。但是构建/链接过程是在Microsoft for VS/Eclipse/Net Beans提供的g++/Visual Studiocompiler中完成的,同样,相信您的工具是broken很可能会引导您远离真正的原因。您犯错误的可能性比编译器导致问题的可能性要大得多。清理解决方案或重新创建生成配置可能会修复生成错误,但这并不意味着编译器中存在错误。链接结果表明这是一个错误,但未确认由Microsoft提供,不可复制。@JDiMatteo此问题有21个答案,因此大量答案不太可能成为解决方案。如果您忽略所有低于您的可能性阈值的答案,则此页面实际上将变得毫无用处,因为大多数常见情况都很容易被发现。我很好奇美国的事实是,在我的例子中,我有一个依赖于共享库的对象文件。我必须修改生成文件,并将库放在Debian上GCC4.8.4的对象之后。在Centos 6.5上GCC4.4的生成文件工作正常。在VS中,与头文件中包含的内容不匹配的cpp文件也属于o类f缺少定义。您应该使用nm-CD或nm-gCD来查看外部符号。另请参见GCC wiki上的。感谢您的系统性解释。在阅读完这篇文章后,我可以理解这里的其他答案。如果您可以明确说明GCC main.c而不是GCC main.c other.c的常见错误,这将是一件好事,初学者通常会这么做在他们的项目变得如此庞大以致于构建.o文件之前,我第一次尝试使用另一个文件中的一些UTIL编写cpp代码,这些步骤帮助我更清楚地理解,所以谢谢!