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


当前回答

需要考虑的一些拼写错误:(我作为初学者经常遇到)

如果您使用的是类:请检查您是否没有在定义函数的cpp文件中的函数名之前忘记“classname::”。如果使用forward声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“结构”而不是“类”。

其他回答

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

#pragma comment(lib, "libname.lib")

除了库路径(包括库的目录)之外,这应该是库的全名。

跨模块.dll(编译器特定)错误地导入/导出方法/类。

MSVS要求您使用__declspec(dllexport)和__declsspec(dllimport)指定要导出和导入的符号。

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

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

宏THIS_MODULE只能在导出函数的模块中定义。这样,声明:

DLLIMPEXP void foo();

扩展到

__declspec(dllexport) void foo();

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

__declspec(dllimport) void foo();

并且告诉编译器,该定义位于链接到的一个库中(另请参见1)。

您可以使用类似的导入/导出类:

class DLLIMPEXP X
{
};

不支持链接器脚本的GNUld包装器

一些.so文件实际上是GNU ld链接器脚本,例如libtbb.so文件是一个ASCII文本文件,其内容如下:

INPUT (libtbb.so.2)

一些更复杂的构建可能不支持这一点。例如,如果在编译器选项中包含-v,则可以看到mainwin gcc包装器mwdip丢弃要链接的库的详细输出列表中的链接器脚本命令文件。

cp libtbb.so.2 libtbb.so

或者可以用.so的完整路径替换-l参数,例如,代替-ltbb-do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtb.so.2

如果所有其他操作都失败,请重新编译。

最近,我只需重新编译有问题的文件,就可以消除Visual Studio 2012中未解决的外部错误。当我重新构建时,错误消失了。

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

班级成员:

纯虚拟析构函数需要实现。

声明析构函数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()声明为纯:

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

可以为类定义中的整型或枚举类型的静态常量数据成员提供初始值设定项;然而,odr使用这个成员仍然需要如上所述的命名空间范围定义。C++11允许在类内初始化所有静态常量数据成员。