提问者:小点点

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


什么是未定义的参考/未解决的外部符号错误? 常见的原因是什么,如何修复/预防?

请随意编辑/添加您自己的内容。


共3个答案

匿名用户

编译C++程序分几个步骤进行,正如2.2所规定的(Keith Thompson的功劳,供参考):

翻译的句法规则的优先顺序由以下几个阶段规定(见脚注)。

  1. 物理源文件字符将以实现定义的方式映射到基本源字符集(必要时为行尾指示符引入新行字符)。 [剪辑]
  2. 删除紧跟在新行字符后面的反斜杠字符(\)的每个实例,拼接物理源行以形成逻辑源行。 [剪辑]
  3. 源文件被分解为预处理令牌(2.5)和空白字符序列(包括注释)。 [剪辑]
  4. 执行
  5. 预处理指令,展开宏调用,执行_pragma一元运算符表达式。 [剪辑]
  6. 将字符文本或字符串文本中的每个源字符集成员,以及字符文本或非原始字符串文本中的每个转义序列和通用字符名转换为执行字符集的相应成员; [剪辑]
  7. 相邻的字符串文字标记是串联的。
  8. 分隔令牌的空白字符不再重要。 每个预处理令牌都转换成令牌。 (2.7)。 得到的令牌在句法和语义上进行分析,并作为一个翻译单元进行翻译。 [剪辑]
  9. 翻译后的翻译单元和实例化单元组合如下:[SNIP]
  10. 解析所有外部实体引用。 库组件被链接以满足对当前翻译中未定义的实体的外部引用。 所有这样的翻译器输出被收集到一个程序映像中,该映像包含在其执行环境中执行所需的信息。 (重点为地雷)

[脚注]尽管在实践中不同的阶段可能被折叠在一起,但实现必须表现得好像发生了这些单独的阶段。

指定的错误发生在编译的最后阶段,通常称为链接。 它基本上意味着您将一堆实现文件编译成对象文件或库,现在您想让它们一起工作。

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

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

其他编译器/平台也存在类似的机制。

常见的错误消息包括Microsoft Visual Studio的error LNK2001error LNK1120error LNK2019以及GCC的符号名的未定义引用。

代码:

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生成以下错误:

/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status

和Microsoft Visual Studio中的类似错误:

1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

常见原因包括:

  • 无法链接到相应的库/对象文件或编译实现文件
  • 已声明和未定义的变量或函数。
  • 类类型成员的常见问题
  • 模板实现不可见。
  • 符号在C程序中定义,并在C++代码中使用。
  • 跨模块/DLL导入/导出方法/类不正确。 (MSVS特定)
  • 循环库依赖项
  • 对`winmain@16'的未定义引用
  • 相互依赖的库顺序
  • 多个同名源文件
  • 使用#pragma(Microsoft Visual Studio)
  • 时键入错误或不包括。lib扩展名
  • 模板朋友的问题
  • 不一致的Unicode定义
  • 常量变量声明/定义中缺少“extern”(仅适用于C++)

匿名用户

声明析构函数为pure仍然需要您定义它(与常规函数不同):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

发生这种情况是因为在隐式销毁对象时调用基类析构函数,因此需要定义。

这类似于没有定义的非虚拟方法,添加了这样的推理,即纯声明生成了一个虚拟vtable,您可能会在不使用函数的情况下获得链接器错误:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

要使其工作,请将x::foo()声明为pure:

struct X
{
    virtual void foo() = 0;
};

某些成员即使没有显式使用也需要定义:

struct A
{ 
    ~A();
};

以下情况将产生错误:

A a;      //destructor undefined

实现可以是内联的,在类定义本身中:

struct A
{ 
    ~A() {}
};

或外部:

A::~A() {}

如果实现在类定义之外,但在头中,则必须将方法标记为内联以防止多重定义。

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

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

定义应为

void A::foo() {}
struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

可以为类定义内的整数或枚举类型的staticeconst数据成员提供初始化器; 但是,对此成员的ODR使用仍然需要如上所述的命名空间范围定义。 C++11允许在类内部对所有static const数据成员进行初始化。

匿名用户

通常,每个翻译单元将生成包含在该翻译单元中定义的符号的定义的目标文件。 要使用这些符号,您必须链接到这些对象文件。

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

g++ -o test objectFile1.o objectFile2.o -lLibraryName

这里的libraryname只是库的名称,没有特定于平台的添加。 因此,例如,在Linux上,库文件通常称为libfoo.So,但您只能编写-lfoo。 在Windows上,该文件可能被称为foo.lib,但您将使用相同的参数。 您可能必须使用-l添加可以找到这些文件的目录。 确保不要在-l-l后面写空格。

对于Xcode:添加用户头搜索路径->; 添加存储库搜索路径->; 将实际库引用拖放到项目文件夹中。

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

还可能发生忘记将文件添加到编译中的情况,在这种情况下,将不会生成对象文件。 在gcc中,您可以将文件添加到命令行中。 在MSVS中,将文件添加到项目将使项目自动编译该文件(尽管可以手动地将文件单独排除在构建之外)。

在Windows编程中,未链接必要库的标志是未解析符号的名称以__imp_开头。 在文档中查找函数的名称,它应该会说出您需要使用哪个库。 例如,MSDN将信息放在每个函数底部的一个框中,该框位于名为“库”的部分中。