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

uinbv5nw  于 2022-09-26  发布在  其他
关注(0)|答案(4)|浏览(265)

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

sbtkgmzw

sbtkgmzw1#

声明但未定义变量或函数。

典型的变量声明是

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

注意,您实现的函数与您声明的函数完全匹配。例如,您的简历限定符可能不匹配:

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)

其他不匹配的例子包括

  • 在一个命名空间中声明的函数/变量,在另一个命名空间中定义。
  • 声明为类成员、定义为全局的函数/变量(反之亦然)。
  • 函数返回类型、参数编号和类型以及调用约定并不完全一致。

来自编译器的错误消息通常会给出已声明但从未定义的变量或函数的完整声明。将其与您提供的定义进行密切比较。确保每个细节都匹配。

7y4bm7vi

7y4bm7vi2#

编译C++程序分几个步骤进行,如2.2(credits to Keith Thompson for the reference)所指定:
翻译的语法规则之间的优先顺序由以下阶段*[见脚注]*规定。

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

  • [脚注]*实现必须表现为这些单独的阶段发生,尽管在实践中不同的阶段可能会合并在一起。

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

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

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

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

常见的错误信息有:Microsoft Visual Studioerror LNK2001error LNK1120error LNK2019GCCundefined reference tosymbol Name

代码:

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

常见原因包括:

r6l8ljro

r6l8ljro3#

类成员:

virtual析构函数需要实现。

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

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数据成员进行初始化。

slmsl1lt

slmsl1lt4#

链接对应的库/对象文件或编译实现文件失败

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

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

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

-l...必须位于任何.o/.c/.cpp文件的右侧。

这里的libraryName只是库的简单名称,没有特定于平台的附加内容。例如,在Linux上,库文件通常称为libfoo.so,但您只需编写-lfoo。在Windows上,相同的文件可能被称为foo.lib,但您将使用相同的参数。您可能需要使用-L‹directory›添加可以找到这些文件的目录。确保在-l-L之后不写入空格。

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

MSVS下,添加到工程中的文件会自动将其目标文件链接在一起,并生成一个lib文件(常用)。要在单独的项目中使用符号,需要在项目设置中包括lib文件。这是在Input -> Additional Dependencies中的项目属性的Linker部分中完成的。(应该在Linker -> General -> Additional Library Directories中添加lib文件的路径)使用随lib文件提供的第三方库时,如果不这样做,通常会导致错误。

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

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

相关问题