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


当前回答

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

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

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

其他回答

不支持链接器脚本的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

对的未定义引用WinMain@16或类似的“不寻常”main()入口点引用(特别是对于visual studio)。

您可能错过了使用实际IDE选择正确的项目类型。IDE可能希望将例如Windows应用程序项目绑定到这样的入口点函数(如上面缺失的引用中所指定的),而不是通常使用的int main(int argc,char**argv);签名

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


下面是从真实世界问题中更详细地处理的案例1和案例2。

UNICODE定义不一致

Windows UNICODE构建时,TCHAR等被定义为wchar_t等。如果不使用UNICODE进行构建,则TCHAR被定义为char等。这些UNICODE和_UNICODE定义会影响所有“t”字符串类型;LPTSTR、LPCTSTR及其麋鹿。

构建一个定义了UNICODE的库,并试图将其链接到未定义UNICODE项目中,将导致链接器错误,因为TCHAR的定义将不匹配;char与wchar_t。

错误通常包括一个带有char或wchar_t派生类型的值的函数,这些类型也可能包括std::basic_string<>等。浏览代码中受影响的函数时,通常会引用TCHAR或std::basic_string<TCHAR>等。这是一个信号,表明代码最初用于UNICODE和多字节字符(或“窄”)构建。

要更正此问题,请使用UNICODE(和_UNICODE)的一致定义构建所有必需的库和项目。

这可以通过以下两种方式实现:;#定义UNICODE#定义UNICODE或在项目设置中;项目财产>常规>项目默认值>字符集或在命令行上;/DUNICODE/D-UNICODE

如果不打算使用UNICODE,请确保未设置定义,和/或在项目中使用了多字符设置,并始终应用。

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

在我的例子中,语法是正确的,但当一个类调用同一DLL中的第二个类时,我出现了错误。第二个类的CPP文件在visual studio中具有错误的财产->项类型,在我的例子中,它被设置为C/C++头,而不是正确的C/C++编译器,因此编译器在构建CPP文件时没有编译它,并导致错误LNK2019

下面是一个示例,假设语法正确,您应该通过更改财产中的项类型来获得错误

//class A header file 
class ClassB; // FORWARD DECLERATION
class ClassA
{
public:
ClassB* bObj;
ClassA(HINSTANCE hDLL) ;
//  member functions
}
--------------------------------
//class A cpp file   
ClassA::ClassA(HINSTANCE hDLL) 
{
    bObj = new ClassB();// LNK2019 occures here 
    bObj ->somefunction();// LNK2019 occures here
}
/*************************/

//classB Header file
struct mystruct{}
class ClassB{
public:
ClassB();
mystruct somefunction();
}

------------------------------
//classB cpp file  
/* This is the file with the wrong property item type in visual studio --C/C++ Header-*/
ClassB::somefunction(){}
ClassB::ClassB(){}

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

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

注意:我使用的是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;
}

and

// 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);
    printf("%d\n", global_var_name);
}

生成对象文件

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

在汇编程序阶段之后,我们有一个对象文件,其中包含要导出的任何符号。看看这些符号

$ 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++ src1.o src2.o -o prog

并运行它

$ ./prog
123

Linker看到导出的符号并将其链接起来

// 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

发生这种情况是因为我们的local_var_name是静态的,即它对其他模块不可见。现在更深入。获取翻译阶段输出

$ 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

所以,我们看到local_var_name没有标签,这就是链接器找不到它的原因。但我们是黑客:),我们可以修复它。在文本编辑器中打开src1.s并更改

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

to

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

也就是说,你应该像下面这样

    .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:
; ...

我们已经更改了localvarname的可见性,并将其值设置为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)

link

$ g++ src1.o src2.o -o prog

并运行它

$ ./prog 
123456789

好的,我们破解它:)

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