什么是未定义的引用/未解析的外部符号错误,我该如何解决?

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


编译C ++程序分为几个步骤,由2.2 指定(Keith Thompson的信用作为参考)

翻译语法规则的优先级由以下阶段指定[见脚注]

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

[脚注]实现必须表现得好像发生了这些不同的阶段,尽管实际上不同的阶段可能会折叠在一起。

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

假设你定义的符号aa.cpp。现在,b.cpp 宣布该符号并使用它。在链接之前,它只是假设该符号已在某处定义,但它还不关心在哪里。链接阶段负责查找符号并将其正确链接到b.cpp(实际上是与使用它的对象或库)。

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

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

常见的错误信息error LNK2001error LNK1120error LNK2019对于微软的Visual Studioundefined reference to 符号名称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

常见原因包括:


virtual析构函数需要实现。

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

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

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

virtual 方法必须实现或定义为纯。

virtual与没有定义的非方法类似,添加的理由是纯声明生成虚拟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()为纯:

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

virtual集体成员

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

struct A
{ 
    ~A();
};

以下将产生错误:

A a;      //destructor undefined

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

struct A
{ 
    ~A() {}
};

或外面:

A::~A() {}

如果实现在类定义之外,但在标题中,则必须将方法标记为inline阻止多重定义。

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

一个常见的错误就是忘记取名这个名字:

struct A
{
   void foo();
};

void foo() {}

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

定义应该是

void A::foo() {}

static数据成员必须在单个翻译单元中的类外定义:

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

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

添加评论

友情链接:蝴蝶教程