问题:什么是未定义的引用/未解决的外部符号错误,如何解决?

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

随时可以编辑/添加自己的内容。

标签:c++,linker-errors,undefined-reference,c++-faq,unresolved-external

Q: What is an undefined reference/unresolved external symbol error and how do I fix it?

What are undefined reference/unresolved external symbol errors? What are common causes and how to fix/prevent them?

Feel free to edit/add your own.

回答1:

按照 2.2 的指定,编译C ++程序需要分几步进行。(请向Keith Thompson致谢参考)

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

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

[脚注] 尽管现实中不同的阶段可能会折叠在一起,但实现的行为必须表现为这些单独的阶段。

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

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

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

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

常见错误消息是 Microsoft Visual Studio errorLNK2001errorLNK1120errorLNK2019对于 GCC 的未定义引用 symbolName

代码:

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

常见原因包括:

A1:

Compiling a C++ program takes place in several steps, as specified by 2.2 (credits to Keith Thompson for the reference):

The precedence among the syntax rules of translation is specified by the following phases [see footnote].

  1. Physical source file characters are mapped, in an implementation-defined manner, to the basic source character set (introducing new-line characters for end-of-line indicators) if necessary. [SNIP]
  2. Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. [SNIP]
  3. The source file is decomposed into preprocessing tokens (2.5) and sequences of white-space characters (including comments). [SNIP]
  4. Preprocessing directives are executed, macro invocations are expanded, and _Pragma unary operator expressions are executed. [SNIP]
  5. Each source character set member in a character literal or a string literal, as well as each escape sequence and universal-character-name in a character literal or a non-raw string literal, is converted to the corresponding member of the execution character set; [SNIP]
  6. Adjacent string literal tokens are concatenated.
  7. White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. (2.7). The resulting tokens are syntactically and semantically analyzed and translated as a translation unit. [SNIP]
  8. Translated translation units and instantiation units are combined as follows: [SNIP]
  9. All external entity references are resolved. Library components are linked to satisfy external references to entities not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment. (emphasis mine)

[footnote] Implementations must behave as if these separate phases occur, although in practice different phases might be folded together.

The specified errors occur during this last stage of compilation, most commonly referred to as linking. It basically means that you compiled a bunch of implementation files into object files or libraries and now you want to get them to work together.

Say you defined symbol a in a.cpp. Now, b.cpp declared that symbol and used it. Before linking, it simply assumes that that symbol was defined somewhere, but it doesn't yet care where. The linking phase is responsible for finding the symbol and correctly linking it to b.cpp (well, actually to the object or library that uses it).

If you're using Microsoft Visual Studio, you'll see that projects generate .lib files. These contain a table of exported symbols, and a table of imported symbols. The imported symbols are resolved against the libraries you link against, and the exported symbols are provided for the libraries that use that .lib (if any).

Similar mechanisms exist for other compilers/ platforms.

Common error messages are error LNK2001, error LNK1120, error LNK2019 for Microsoft Visual Studio and undefined reference to symbolName for GCC.

The code:

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;
}

will generate the following errors with 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

and similar errors with 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

Common causes include:

回答2:

班级成员:

虚拟析构函数需要实现。

声明纯析构函数仍然需要您对其进行定义(与常规函数不同):

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

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

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

这类似于没有定义的非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;
};

非-虚拟类成员

即使没有明确使用,也需要定义一些成员:

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() {}

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

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

可以为类定义中整数或枚举类型的static const数据成员提供初始化程序;但是,对该成员的odr-use仍然需要如上所述的名称空间范围定义。 C ++ 11允许在类内部对所有staticconst数据成员进行初始化。

A2:

Class members:

A pure virtual destructor needs an implementation.

Declaring a destructor pure still requires you to define it (unlike a regular function):

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

This happens because base class destructors are called when the object is destroyed implicitly, so a definition is required.

virtual methods must either be implemented or defined as pure.

This is similar to non-virtual methods with no definition, with the added reasoning that the pure declaration generates a dummy vtable and you might get the linker error without using the function:

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

For this to work, declare X::foo() as pure:

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

Non-virtual class members

Some members need to be defined even if not used explicitly:

struct A
{ 
    ~A();
};

The following would yield the error:

A a;      //destructor undefined

The implementation can be inline, in the class definition itself:

struct A
{ 
    ~A() {}
};

or outside:

A::~A() {}

If the implementation is outside the class definition, but in a header, the methods have to be marked as inline to prevent a multiple definition.

All used member methods need to be defined if used.

A common mistake is forgetting to qualify the name:

struct A
{
   void foo();
};

void foo() {}

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

The definition should be

void A::foo() {}

static data members must be defined outside the class in a single translation unit:

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

An initializer can be provided for a static const data member of integral or enumeration type within the class definition; however, odr-use of this member will still require a namespace scope definition as described above. C++11 allows initialization inside the class for all static const data members.

回答3:

无法链接到适当的库/目标文件或编译实现文件

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

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

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

此处的libraryName只是库的裸名,没有特定于平台的附加内容。所以例如在Linux库文件上,文件通常称为libfoo.so,但是您只需编写-lfoo。在Windows上,同一文件可能称为foo.lib,但是您将使用相同的参数。您可能必须使用-L‹directory›添加可以在其中找到这些文件的目录。确保不要在-l<​​/code>或-L之后写空格。

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

MSVS 下,添加到项目中的文件会自动将其目标文件链接在一起,并且会生成一个lib文件(通常使用)。要在单独的项目中使用这些符号,您需要在项目设置中包含lib文件。这是在项目属性的链接器部分的Input->AdditionalDependencies中完成的。 (应在Linker->General->AdditionalLibraryDirectories中添加lib文件的路径)当使用随一起提供的第三方库时lib 文件,否则通常会导致错误。

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

在Windows编程中,您没有链接必要库的迹象表明,未解析符号的名称以__imp_开头。在文档中查找该函数的名称,并应说明您需要使用哪个库。例如,MSDN将信息放在每个函数底部"方框"的框中。

A3:

Failure to link against appropriate libraries/object files or compile implementation files

Commonly, each translation unit will generate an object file that contains the definitions of the symbols defined in that translation unit. To use those symbols, you have to link against those object files.

Under gcc you would specify all object files that are to be linked together in the command line, or compile the implementation files together.

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

The libraryName here is just the bare name of the library, without platform-specific additions. So e.g. on Linux library files are usually called libfoo.so but you'd only write -lfoo. On Windows that same file might be called foo.lib, but you'd use the same argument. You might have to add the directory where those files can be found using -L‹directory›. Make sure to not write a space after -l or -L.

For XCode: Add the User Header Search Paths -> add the Library Search Path -> drag and drop the actual library reference into the project folder.

Under MSVS, files added to a project automatically have their object files linked together and a lib file would be generated (in common usage). To use the symbols in a separate project, you'd need to include the lib files in the project settings. This is done in the Linker section of the project properties, in Input -> Additional Dependencies. (the path to the lib file should be added in Linker -> General -> Additional Library Directories) When using a third-party library that is provided with a lib file, failure to do so usually results in the error.

It can also happen that you forget to add the file to the compilation, in which case the object file won't be generated. In gcc you'd add the files to the command line. In MSVS adding the file to the project will make it compile it automatically (albeit files can, manually, be individually excluded from the build).

In Windows programming, the tell-tale sign that you did not link a necessary library is that the name of the unresolved symbol begins with __imp_. Look up the name of the function in the documentation, and it should say which library you need to use. For example, MSDN puts the information in a box at the bottom of each function in a section called "Library".

回答4:

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

典型的变量声明是

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

请注意,您实现的功能与您声明的功能完全匹配。例如,您的cv限定词可能不匹配:

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)

不匹配的其他示例包括

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

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

A4:

Declared but did not define a variable or function.

A typical variable declaration is

extern int x;

As this is only a declaration, a single definition is needed. A corresponding definition would be:

int x;

For example, the following would generate an error:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

Similar remarks apply to functions. Declaring a function without defining it leads to the error:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

Be careful that the function you implement exactly matches the one you declared. For example, you may have mismatched cv-qualifiers:

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)

Other examples of mismatches include

  • Function/variable declared in one namespace, defined in another.
  • Function/variable declared as class member, defined as global (or vice versa).
  • Function return type, parameter number and types, and calling convention do not all exactly agree.

The error message from the compiler will often give you the full declaration of the variable or function that was declared but never defined. Compare it closely to the definition you provided. Make sure every detail matches.

回答5:

指定相互依赖的链接库的顺序是错误的。

如果库相互依赖,则链接库的顺序确实很重要。通常,如果库A依赖于库B,则libA 必须出现在libB。

例如:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

创建库:

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

编译:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out

因此要再次重复,顺序确实很重要!

A5:

The order in which interdependent linked libraries are specified is wrong.

The order in which libraries are linked DOES matter if the libraries depend on each other. In general, if library A depends on library B, then libA MUST appear before libB in the linker flags.

For example:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

Create the libraries:

$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

Compile:

$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out

So to repeat again, the order DOES matter!

回答6:

什么是"未定义的引用/无法解析的外部符号"

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

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

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

链接器看到导出的符号并将其链接。现在,我们尝试在src2.cpp中取消注释,就像这里

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

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

我们已经更改了local_var_name的可见性并将其值设置为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具有Bind GLOBAL(是LOCAL)

链接

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

并运行它

$ ./prog 
123456789

好的,我们破解它:)

因此,当链接器在目标文件中找不到全局符号时,发生"未定义的引用/未解决的外部符号错误"。

A6:

what is an "undefined reference/unresolved external symbol"

I'll try to explain what is an "undefined reference/unresolved external symbol".

note: i use g++ and Linux and all examples is for it

For example we have some code

// 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);
}

Make object files

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

After the assembler phase we have an object file, which contains any symbols to export. Look at the symbols

$ 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]

I've rejected some lines from output, because they do not matter

So, we see follow symbols to export.

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cpp exports nothing and we have seen no its symbols

Link our object files

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

and run it

$ ./prog
123

Linker sees exported symbols and links it. Now we try to uncomment lines in src2.cpp like here

// 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);
}

and rebuild an object file

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

OK (no errors), because we only build object file, linking is not done yet. Try to link

$ 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

It has happened because our local_var_name is static, i.e. it is not visible for other modules. Now more deeply. Get the translation phase output

$ 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

So, we've seen there is no label for local_var_name, that's why linker hasn't found it. But we are hackers :) and we can fix it. Open src1.s in your text editor and change

.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

i.e. you should have like below

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

we have changed the visibility of local_var_name and set its value to 456789. Try to build an object file from it

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

ok, see readelf output (symbols)

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

now local_var_name has Bind GLOBAL (was LOCAL)

link

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

and run it

$ ./prog 
123456789

ok, we hack it :)

So, as a result - an "undefined reference/unresolved external symbol error" happens when the linker cannot find global symbols in the object files.

回答7:

符号在C程序中定义并在C ++代码中使用。

函数(或变量)voidfoo()是在C程序中定义的,而您尝试在C ++程序中使用它:

void foo();
int main()
{
    foo();
}

C ++链接器期望名称被修饰,因此您必须将函数声明为:

extern "C" void foo();
int main()
{
    foo();
}

等效地,函数(或变量)voidfoo()不是在C程序中定义的,而是在C ++中定义的,但具有C链接:

extern "C" void foo();

,您尝试在具有C ++链接的C ++程序中使用它。

如果整个库包含在头文件中(并已编译为C代码);包含内容必须如下;

extern "C" {
    #include "cheader.h"
}

A7:

Symbols were defined in a C program and used in C++ code.

The function (or variable) void foo() was defined in a C program and you attempt to use it in a C++ program:

void foo();
int main()
{
    foo();
}

The C++ linker expects names to be mangled, so you have to declare the function as:

extern "C" void foo();
int main()
{
    foo();
}

Equivalently, instead of being defined in a C program, the function (or variable) void foo() was defined in C++ but with C linkage:

extern "C" void foo();

and you attempt to use it in a C++ program with C++ linkage.

If an entire library is included in a header file (and was compiled as C code); the include will need to be as follows;

extern "C" {
    #include "cheader.h"
}

回答8:

如果其他所有方法均失败,请重新编译。

我最近能够通过重新编译有问题的文件来摆脱Visual Studio 2012中未解决的外部错误。当我重建时,错误消失了。

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

A8:

If all else fails, recompile.

I was recently able to get rid of an unresolved external error in Visual Studio 2012 just by recompiling the offending file. When I re-built, the error went away.

This usually happens when two (or more) libraries have a cyclic dependency. Library A attempts to use symbols in B.lib and library B attempts to use symbols from A.lib. Neither exist to start off with. When you attempt to compile A, the link step will fail because it can't find B.lib. A.lib will be generated, but no dll. You then compile B, which will succeed and generate B.lib. Re-compiling A will now work because B.lib is now found.

回答9:

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

MSVS要求您使用__declspec(dllexport)__declspec(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
{
};

A9:

Incorrectly importing/exporting methods/classes across modules/dll (compiler specific).

MSVS requires you to specify which symbols to export and import using __declspec(dllexport) and __declspec(dllimport).

This dual functionality is usually obtained through the use of a macro:

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

The macro THIS_MODULE would only be defined in the module that exports the function. That way, the declaration:

DLLIMPEXP void foo();

expands to

__declspec(dllexport) void foo();

and tells the compiler to export the function, as the current module contains its definition. When including the declaration in a different module, it would expand to

__declspec(dllimport) void foo();

and tells the compiler that the definition is in one of the libraries you linked against (also see 1)).

You can similary import/export classes:

class DLLIMPEXP X
{
};

回答10:

这是每个VC ++程序员一次又一次看到的最令人困惑的错误消息之一。首先让我们弄清楚。

A。什么是符号?简而言之,符号就是名称。它可以是变量名称,函数名称,类名称,typedef名称或除属于C ++语言的那些名称和符号以外的任何名称。它是由用户定义或由依赖项库(另一个用户定义)引入的。

B。什么是外部的?在VC ++中,每个源文件(.cpp,.c等)都被视为翻译单元,编译器一次编译一个单元,并为该文件生成一个目标文件(.obj)。当前的翻译单位。 (请注意,此源文件中包含的每个头文件都将被预处理,并将被视为此翻译单元的一部分。)翻译单元中的所有内容均被视为内部文件,其他所有内容均被视为外部文件。在C ++中,您可以使用extern__declspec(dllimport)等关键字来引用外部符号。

C。什么是"解决"?"解决"是一个链接时术语。在链接时,链接器尝试为无法在内部找到其定义的目标文件中的每个符号查找外部定义。此搜索过程的范围包括:

  • 在编译时生成的所有目标文件
  • 显式或隐式指定为该建筑应用程序的其他依赖项的所有库(.lib)。

此搜索过程称为"解决"。

D。最后,为什么要使用未解析的外部符号?如果链接器找不到内部没有定义的符号的外部定义,则会报告"未解析的外部符号"错误。

E。 LNK2019的可能原因:未解决的外部符号错误。我们已经知道此错误是由于链接器未能找到外部符号的定义,可能的原因可以归类为:

  1. 存在定义

例如,如果我们在a.cpp中定义了一个名为foo的函数:

int foo()
{
    return 0;
}

在b.cpp中,我们要调用函数foo,因此我们添加

void foo();

声明函数foo(),并在另一个函数体中调用它,例如bar()

void bar()
{
    foo();
}

现在,当您构建此代码时,您会收到LNK2019错误,抱怨foo是未解决的符号。在这种情况下,我们知道foo()在a.cpp中有其定义,但与我们正在调用的定义不同(不同的返回值)。定义存在就是这种情况。

  1. 定义不存在

如果我们要调用库中的某些函数,但导入库未添加到其他依赖项列表中(从以下位置进行设置:项目|属性|配置属性|链接器|输入|其他依赖项 )的项目设置。现在,链接器将报告LNK2019,因为当前搜索范围中不存在该定义。

A10:

This is one of most confusing error messages that every VC++ programmers have seen time and time again. Let’s make things clarity first.

A. What is symbol? In short, a symbol is a name. It can be a variable name, a function name, a class name, a typedef name, or anything except those names and signs that belong to C++ language. It is user defined or introduced by a dependency library (another user-defined).

B. What is external? In VC++, every source file (.cpp,.c,etc.) is considered as a translation unit, the compiler compiles one unit at a time, and generate one object file(.obj) for the current translation unit. (Note that every header file that this source file included will be preprocessed and will be considered as part of this translation unit)Everything within a translation unit is considered as internal, everything else is considered as external. In C++, you may reference an external symbol by using keywords like extern, __declspec (dllimport) and so on.

C. What is “resolve”? Resolve is a linking-time term. In linking-time, linker attempts to find the external definition for every symbol in object files that cannot find its definition internally. The scope of this searching process including:

  • All object files that generated in compiling time
  • All libraries (.lib) that are either explicitly or implicitly specified as additional dependencies of this building application.

This searching process is called resolve.

D. Finally, why Unresolved External Symbol? If the linker cannot find the external definition for a symbol that has no definition internally, it reports an Unresolved External Symbol error.

E. Possible causes of LNK2019: Unresolved External Symbol error. We already know that this error is due to the linker failed to find the definition of external symbols, the possible causes can be sorted as:

  1. Definition exists

For example, if we have a function called foo defined in a.cpp:

int foo()
{
    return 0;
}

In b.cpp we want to call function foo, so we add

void foo();

to declare function foo(), and call it in another function body, say bar():

void bar()
{
    foo();
}

Now when you build this code you will get a LNK2019 error complaining that foo is an unresolved symbol. In this case, we know that foo() has its definition in a.cpp, but different from the one we are calling(different return value). This is the case that definition exists.

  1. Definition does not exist

If we want to call some functions in a library, but the import library is not added into the additional dependency list (set from: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency) of your project setting. Now the linker will report a LNK2019 since the definition does not exist in current searching scope.

回答11:

模板实现不可见。

未专业化的模板的定义必须对使用它们的所有翻译单位可见。这意味着您无法将模板的定义与实现文件分开。如果必须分开实现,通常的解决方法是在声明模板的标头末尾包含一个impl文件。常见的情况是:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

要解决此问题,必须将X::foo的定义移至头文件或使用该文件的翻译单元可见的某个位置。

可以在实现文件中实现专业化模板,并且实现不一定必须是可见的,但是必须事先声明专业化。

有关进一步的说明和其他可能的解决方案(显式实例化),请参见此问题和答案

A11:

Template implementations not visible.

Unspecialized templates must have their definitions visible to all translation units that use them. That means you can't separate the definition of a template to an implementation file. If you must separate the implementation, the usual workaround is to have an impl file which you include at the end of the header that declares the template. A common situation is:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

To fix this, you must move the definition of X::foo to the header file or some place visible to the translation unit that uses it.

Specialized templates can be implemented in an implementation file and the implementation doesn't have to be visible, but the specialization must be previously declared.

For further explanation and another possible solution (explicit instantiation) see this question and answer.

回答12:

WinMain@16或类似的'unusual' main()入口点引用的未定义引用(尤其是对于)。< / p>

您可能错过了使用实际IDE选择正确的项目类型的信息。 IDE可能想绑定例如Windows应用程序将投影到此类入口点功能(如上面缺少的参考中所指定),而不是常用的intmain(intargc,char**argv);签名。

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


这是 case1 case2 现实世界中进行了更详细的处理>问题。

A12:

undefined reference to WinMain@16 or similar 'unusual' main() entry point reference (especially for ).

You may have missed to choose the right project type with your actual IDE. The IDE may want to bind e.g. Windows Application projects to such entry point function (as specified in the missing reference above), instead of the commonly used int main(int argc, char** argv); signature.

If your IDE supports Plain Console Projects you might want to choose this project type, instead of a windows application project.


Here are case1 and case2 handled in more detail from a real world problem.

回答13:

如果您使用的是第三方库,请确保您拥有正确的32/64位二进制文​​件

A13:

Also if you're using 3rd party libraries make sure you have the correct 32/64 bit binaries

回答14:

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

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

除了包含库目录的库路径之外,这还应该是库的全名。

A14:

Microsoft offers a #pragma to reference the correct library at link time;

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

In addition to the library path including the directory of the library, this should be the full name of the library.

回答15:

需要为新的工具集版本更新Visual Studio NuGet程序包

尝试将libpng与Visual Studio 2013链接时,我遇到了这个问题。问题是该程序包文件仅包含Visual Studio 2010和2012的库。

正确的解决方案是希望开发人员发布更新的程序包然后进行升级,但是它通过对VS2012的一个额外设置(指向VS2012库文件)进行入侵而为我工作。

我通过找到packagename\build\native\packagename.targets并在该文件内,将所有文件复制到其中,从而编辑了该程序包(在解决方案目录内的packages文件夹中)。 v110部分。我在仅条件字段中将v110更改为v120,非常小心地将文件名路径全部保留为v110 。这仅允许Visual Studio 2013链接到2012年的库,在这种情况下,它可以工作。

A15:

Visual Studio NuGet package needs to be updated for new toolset version

I just had this problem trying to link libpng with Visual Studio 2013. The problem is that the package file only had libraries for Visual Studio 2010 and 2012.

The correct solution is to hope the developer releases an updated package and then upgrade, but it worked for me by hacking in an extra setting for VS2013, pointing at the VS2012 library files.

I edited the package (in the packages folder inside the solution's directory) by finding packagename\build\native\packagename.targets and inside that file, copying all the v110 sections. I changed the v110 to v120 in the condition fields only being very careful to leave the filename paths all as v110. This simply allowed Visual Studio 2013 to link to the libraries for 2012, and in this case, it worked.

回答16:

假设您有一个用c ++编写的大型项目,其中包含一千个.cpp文件和一千个.h文件,并且说该项目还依赖于十个静态库。假设我们在Windows上,并且在Visual Studio 20xx中构建了项目。当您按Ctrl + F7 Visual Studio开始编译整个解决方案时(假设我们在解决方案中只有一个项目)

编译的含义是什么?

  • Visual Studio搜索文件 .vcxproj ,然后开始编译扩展名为.cpp的每个文件。编译顺序是不确定的。因此,您不能假定文件main.cpp首先被编译
  • 如果.cpp文件依赖于其他.h文件以便查找可能在文件.cpp中定义或未定义的符号
  • 如果存在一个.cpp文件,编译器在其中找不到一个符号,则编译器时间错误会显示一条消息:找不到符号x
  • 为每个扩展名为.cpp的文件生成一个目标文件.o,Visual Studio还将输出写入名为 ProjectName.Cpp.Clean.txt 的文件中,该文件包含必须由链接器处理。

第二步由Linker完成.Linker应该合并所有目标文件并最终构建输出(可以是可执行文件或库)

链接项目的步骤

  • 解析所有目标文件并找到仅在标头中声明的定义(例如:前面答案中提到的类的一种方法的代码,或者事件是类中成员的静态变量的初始化) )
  • 如果在目标文件中找不到一个符号,则还将在"其他库"中进行搜索。将新库添加到项目中配置属性-> VC ++目录- > 库目录,在这里您指定了其他文件夹来搜索库和配置属性-> 链接器-> 输入指定库的名称。 -如果链接器在一个.cpp文件中找不到您写的符号,他将引发一个链接器时间错误,听起来像是errorLNK2001:未解析的外部符号"void__cdeclfoo(void)"(?foo @@ YAXXZ)

观察

  1. 链接器找到一个符号后,便不会在其他库中搜索该符号
  2. 链接库的顺序很重要
  3. 如果Linker在一个静态库中找到一个外部符号,则在项目的输出中包括该符号;但是,如果该库是shared(dynamic),则在输出中不包括该代码(symbol),但是运行时崩溃可能会发生

如何解决此类错误

编译器时间错误:

  • 确保您正确编写了c ++项目的语法。

链接器时间错误

  • 定义在头文件中声明的所有符号
  • 使用一次#pragma允许编译器不包含一个标头(如果已将其包含在当前已编译的.cpp中)
  • 确保您的外部库中不包含可能与您在头文件中定义的其他符号发生冲突的符号
  • 使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例生成适当的代码。

A16:

Suppose you have a big project written in c++ which has a thousand of .cpp files and a thousand of .h files.And let's says the project also depends on ten static libraries. Let's says we are on Windows and we build our project in Visual Studio 20xx. When you press Ctrl + F7 Visual Studio to start compiling the whole solution ( suppose we have just one project in the solution )

What's the meaning of compilation ?

  • Visual Studio search into file .vcxproj and start compiling each file which has the extension .cpp. Order of compilation is undefined.So you must not assume that the file main.cpp is compiled first
  • If .cpp files depends on additional .h files in order to find symbols that may or may not be defined in the file .cpp
  • If exists one .cpp file in which the compiler could not find one symbol, a compiler time error raises the message Symbol x could not be found
  • For each file with extension .cpp is generated an object file .o and also Visual Studio writes the output in a file named ProjectName.Cpp.Clean.txt which contains all object files that must be processed by the linker.

The Second step of compilation is done by Linker.Linker should merge all the object file and build finally the output ( which may be an executable or a library)

Steps In Linking a project

  • Parse all the object files and find the definition which was only declared in headers ( eg: The code of one method of a class as is mentioned in previous answers, or event the initialization of a static variable which is member inside a class)
  • If one symbol could not be found in object files he also is searched in Additional Libraries.For adding a new library to a project Configuration properties -> VC++ Directories -> Library Directories and here you specified additional folder for searching libraries and Configuration properties -> Linker -> Input for specifying the name of the library. -If the Linker could not find the symbol which you write in one .cpp he raises a linker time error which may sound like error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observation

  1. Once the Linker find one symbol he doesn't search in other libraries for it
  2. The order of linking libraries does matter.
  3. If Linker finds an external symbol in one static library he includes the symbol in the output of the project.However, if the library is shared( dynamic ) he doesn't include the code ( symbols ) in output, but Run-Time crashes may occur

How To Solve this kind of error

Compiler Time Error :

  • Make sure you write your c++ project syntactical correct.

Linker Time Error

  • Define all your symbol which you declare in your header files
  • Use #pragma once for allowing compiler not to include one header if it was already included in the current .cpp which are compiled
  • Make sure that your external library doesn't contain symbols that may enter into conflict with other symbols you defined in your header files
  • When you use the template to make sure you include the definition of each template function in the header file for allowing the compiler to generate appropriate code for any instantiations.

回答17:

编译器/ IDE中的错误

我最近遇到了这个问题,结果它是Visual Studio Express 2013中的错误。我不得不从项目中删除源文件,然后重新添加它以解决该错误。

如果您认为它可能是编译器/ IDE中的错误,请尝试以下步骤:

  • 清理项目(某些IDE可以选择执行此操作,您也可以通过删除目标文件来手动执行此操作)
  • 尝试启动一个新项目,复制原始项目中的所有源代码。

A17:

A bug in the compiler/IDE

I recently had this problem, and it turned out it was a bug in Visual Studio Express 2013. I had to remove a source file from the project and re-add it to overcome the bug.

Steps to try if you believe it could be a bug in compiler/IDE:

  • Clean the project (some IDEs have an option to do this, you can also manually do it by deleting the object files)
  • Try start a new project, copying all source code from the original one.

回答18:

使用链接器帮助诊断错误

大多数现代链接器都包含一个详细的选项,可以不同程度地打印出来;

  • 链接调用(命令行)
  • 有关链接阶段包含哪些库的数据,
  • 库的位置,
  • 使用的搜索路径。

对于gcc和clang;您通常会在命令行中添加-v-Wl,-verbose-v-Wl,-v。可以在此处找到更多详细信息;

对于MSVC,将/VERBOSE(尤其是/VERBOSE:LIB)添加到链接命令行。

A18:

Use the linker to help diagnose the error

Most modern linkers include a verbose option that prints out to varying degrees;

  • Link invocation (command line),
  • Data on what libraries are included in the link stage,
  • The location of the libraries,
  • Search paths used.

For gcc and clang; you would typically add -v -Wl,--verbose or -v -Wl,-v to the command line. More details can be found here;

For MSVC, /VERBOSE (in particular /VERBOSE:LIB) is added to the link command line.

回答19:

链接的.lib文件与一个.dll相关联

我有同样的问题。说我有MyProject和TestProject项目。我已经有效地将MyProject的lib文件链接到TestProject。但是,此lib文件是在为MyProject构建DLL时生成的。另外,我没有包含MyProject中所有方法的源代码,而仅包含对DLL入口点的访问。

为解决此问题,我将MyProject构建为LIB,并将TestProject链接到此.lib文件(我将生成的.lib文件复制粘贴到TestProject文件夹中)。然后,我可以再次将MyProject构建为DLL。它正在编译,因为链接到TestProject的库确实包含MyProject类中所有方法的代码。

A19:

Linked .lib file is associated to a .dll

I had the same issue. Say i have projects MyProject and TestProject. I had effectively linked the lib file for MyProject to the TestProject. However, this lib file was produced as the DLL for the MyProject was built. Also, I did not contain source code for all methods in the MyProject, but only access to the DLL's entry points.

To solve the issue, i built the MyProject as a LIB, and linked TestProject to this .lib file (i copy paste the generated .lib file into the TestProject folder). I can then build again MyProject as a DLL. It is compiling since the lib to which TestProject is linked does contain code for all methods in classes in MyProject.

回答20:

由于涉及链接器错误时人们似乎直接针对此问题,因此我将在此处添加。

使用GCC 5.2.0导致链接器错误的一个可能原因是,默认情况下现在选择了新的libstdc ++库ABI。

如果收到有关在std :: __ cxx11命名空间或标记[abi:cxx11]中涉及类型的符号的未定义引用的链接器错误,则可能表明您正在尝试将使用不同值编译的目标文件链接在一起_GLIBCXX_USE_CXX11_ABI宏。当链接到使用旧版GCC编译的第三方库时,通常会发生这种情况。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码。

因此,如果您在5.1.0之后切换到GCC时突然收到链接器错误,那就需要检查一下。

A20:

Since people seem to be directed to this question when it comes to linker errors I am going to add this here.

One possible reason for linker errors with GCC 5.2.0 is that a new libstdc++ library ABI is now chosen by default.

If you get linker errors about undefined references to symbols that involve types in the std::__cxx11 namespace or the tag [abi:cxx11] then it probably indicates that you are trying to link together object files that were compiled with different values for the _GLIBCXX_USE_CXX11_ABI macro. This commonly happens when linking to a third-party library that was compiled with an older version of GCC. If the third-party library cannot be rebuilt with the new ABI then you will need to recompile your code with the old ABI.

So if you suddenly get linker errors when switching to a GCC after 5.1.0 this would be a thing to check out.

回答21:

围绕GNU ld的包装,不支持链接描述文件

某些.so文件实际上是 GNU ld链接程序脚本< / a>,例如 libtbb.so 文件是具有以下内容的ASCII文本文件:

INPUT (libtbb.so.2)

一些更复杂的版本可能不支持此功能。例如,如果在编译器选项中包含-v,则可以看到 mainwin gcc包装器mwdip 放弃要链接的库的详细输出列表中的链接器脚本命令文件。一种简单的解决方法是用文件的副本(或符号链接)代替链接器脚本输入命令文件,例如

cp libtbb.so.2 libtbb.so

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

A21:

A wrapper around GNU ld that doesn't support linker scripts

Some .so files are actually GNU ld linker scripts, e.g. libtbb.so file is an ASCII text file with this contents:

INPUT (libtbb.so.2)

Some more complex builds may not support this. For example, if you include -v in the compiler options, you can see that the mainwin gcc wrapper mwdip discards linker script command files in the verbose output list of libraries to link in. A simple work around is to replace the linker script input command file with a copy of the file instead (or a symlink), e.g.

cp libtbb.so.2 libtbb.so

Or you could replace the -l argument with the full path of the .so, e.g. instead of -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

回答22:

成为朋友的模板...

使用朋友运算符(或函数)给出模板类型的代码段;

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

operator<<被声明为非模板函数。对于与Foo一起使用的每种T类型,都需要一个非模板化的operator<<。例如,如果声明了Foo 类型,则必须存在如下的运算符实现;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

由于未实现,链接器无法找到它并导致错误。

要解决此问题,可以在Foo类型之前声明一个模板运算符,然后将适当的实例声明为好友。语法有点尴尬,但外观如下;

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

上面的代码将操作员的友情限制为Foo的相应实例,即,operator<< 实例化仅限于访问的私有成员。 Foo 的实例化。

替代品包括;

  • 允许将友谊扩展到模板的所有实例,如下所示;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • 或者,operator<<的实现可以在类定义内进行内联;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };
    

注意,当运算符(或函数)的声明仅出现在类中时,该名称不可用于"常规"查找,而仅用于依赖于参数的查找,来自 cppreference ;

首先在类或类模板X的朋友声明中声明的名称成为X的最内层命名空间的成员,但不可用于查找(除非考虑X的依赖于参数的查找除外),除非在提供了命名空间范围...

cppreference 和< a href=" https://isocpp.org/wiki/faq/templates#template-friends" rel="noreferrer"> C ++常见问题解答。

显示上述技术的代码列表


作为失败代码示例的注释; g ++对此警告如下

警告:朋友声明'std::ostream&operator<<(...)'声明一个非模板函数[-Wnon-template-friend]

注意:(如果这不是您想要的,请确保已经声明了功能模板,并在此处在功能名称后添加<>)

A22:

Befriending templates...

Given the code snippet of a template type with a friend operator (or function);

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

The operator<< is being declared as a non-template function. For every type T used with Foo, there needs to be a non-templated operator<<. For example, if there is a type Foo<int> declared, then there must be an operator implementation as follows;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

Since it is not implemented, the linker fails to find it and results in the error.

To correct this, you can declare a template operator before the Foo type and then declare as a friend, the appropriate instantiation. The syntax is a little awkward, but is looks as follows;

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

The above code limits the friendship of the operator to the corresponding instantiation of Foo, i.e. the operator<< <int> instantiation is limited to access the private members of the instantiation of Foo<int>.

Alternatives include;

  • Allowing the friendship to extend to all instantiations of the templates, as follows;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • Or, the implementation for the operator<< can be done inline inside the class definition;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };
    

Note, when the declaration of the operator (or function) only appears in the class, the name is not available for "normal" lookup, only for argument dependent lookup, from cppreference;

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not accessible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided...

There is further reading on template friends at cppreference and the C++ FAQ.

Code listing showing the techniques above.


As a side note to the failing code sample; g++ warns about this as follows

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

回答23:

您的链接在引用它们的目标文件之前先消耗了库

  • 您正在尝试编译程序并将其与GCC工具链链接。
  • 您的链接指定了所有必需的库和库搜索路径
  • 如果libfoo依赖于libbar,则您的链接正确地将libfoo放在libbar之前。
  • 您的链接失败,并出现未定义的引用 某事错误。
  • 但是所有未定义的东西都在您已#include d的头文件中声明,并且实际上是在您链接的库中定义的。

示例在C中。它们同样可能是C ++

涉及您自己构建的静态库的最小示例

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

您将构建静态库:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

您编译程序:

$ gcc -I. -c -o eg1.o eg1.c

您尝试将其与libmy_lib.a链接并失败:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

如果一步编译和链接,将得到相同的结果,例如:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

一个涉及共享系统库的最小示例,压缩库libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

编译程序:

$ gcc -c -o eg2.o eg2.c

尝试将您的程序与libz链接并失败:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

与一次编译和链接相同:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

示例2的变体,其中涉及pkg-config

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

您在做什么错了?

在要链接以创建程序的目标文件和库的顺序中,将库放置在引用它们的目标文件之前。您需要将库放在引用它们的对象文件之后

正确链接示例1:

$ gcc -o eg1 eg1.o -L. -lmy_lib

成功:

$ ./eg1 
Hello World

正确链接示例2:

$ gcc -o eg2 eg2.o -lz

成功:

$ ./eg2 
1.2.8

正确链接示例2 pkg-config的变体:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

说明

从此处开始阅读是可选的

默认情况下,GCC在您的发行版上生成的链接命令会按命令行顺序从左到右使用链接中的文件。当发现文件引用了某物并且不包含其定义时,将在右侧的文件中搜索定义。如果最终找到定义,则引用将被解析。如果最后没有解决任何引用,则链接失败:链接器不会向后搜索。

首先,示例1 ,带有静态库my_lib.a

静态库是对象文件的索引存档。当链接程序在链接顺序中找到-lmy_lib并发现它引用静态库./libmy_lib.a时,它想知道您的程序是否需要以下任何一项: libmy_lib.a中的目标文件。

libmy_lib.a中只有一个目标文件,即my_lib.o,而在my_lib.o中定义的只有一件事,即功能hw

当且仅当它已经知道您的程序在一个或多个目标文件中引用了hw时,链接程序才会确定您的程序需要my_lib.o它已经添加到程序中,并且已经添加的所有目标文件都不包含hw的定义。

如果这是真的,则链接器将从库中提取my_lib.o的副本并将其添加到您的程序中。然后,您的程序包含hw的定义,因此对hw的引用将被解析

当您尝试链接程序时,例如:

$ gcc -o eg1 -L. -lmy_lib eg1.o

链接器尚未添加 eg1.o 到程序,当它看到-lmy_lib时。因为到那时,它还没有看到eg1.o。您的程序尚未对hw进行任何引用:它尚未对进行任何引用,因为它所做的所有引用都在eg1.o

因此,链接程序不会将my_lib.o添加到程序中,并且对于libmy_lib.a也没有进一步的用途。

接下来,它找到eg1.o,并将其添加为程序。链接序列中的目标文件总是添加到程序中。现在,该程序引用了hw,并且不包含hw的定义;但是链接序列中没有任何东西可以提供缺少的定义。对hw的引用以 unresolved 结尾,并且链接失败。

第二,示例2 ,使用共享库libz

共享库不是目标文件或类似文件的存档。它更像是一个程序,它没有main函数,而是公开了它定义的多个其他符号,以便其他程序可以在运行时使用它们。

当今许多Linux发行版都配置其GCC工具链,以便其语言驱动程序(gccg++gfortran等)指示系统链接器( ld)以根据需要链接共享库。您有其中一个发行版。

这意味着当链接程序在链接序列中找到-lz时,发现它指向共享库(例如)/usr/lib/x86_64-linux-gnu/libz.so ,它想知道它尚未添加到您程序中的任何未定义引用都具有由libz

导出的定义

如果是这样,则链接器将libz中复制任何块并将其添加到您的程序中;相反,它只会修改您的程序代码,以便:-

  • 在运行时,只要加载程序的副本,系统程序加载器就会将libz的副本加载到与您的程序相同的进程中。

  • 在运行时,只要您的程序引用libz中定义的内容,该引用就在同一过程中使用libz副本导出的定义。

您的程序只想引用一个具有由libz导出的定义的东西,即函数zlibVersion,在中仅被引用一次.eg2.c 。如果链接器将该引用添加到您的程序,然后找到libz导出的定义,则该引用是 resolved

但是当您尝试像这样链接程序时:

gcc -o eg2 -lz eg2.o

事件的顺序是错误的,与示例1相同。在链接器找到-lz时,没有 no 引用任何内容。该程序:它们都位于eg2.o中,但尚未见过。因此,链接器决定它对libz没有用。当它到达eg2.o并将其添加到程序中,然后具有对zlibVersion的未定义引用时,链接序列完成;该引用未解决,并且链接失败。

最后,示例2的pkg-config变体现在有了明显的解释。展开外壳后:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

成为:

gcc -o eg2 -lz eg2.o

这只是示例2。

我可以在示例1中重现该问题,但不能在示例2中重现

链接:

gcc -o eg2 -lz eg2.o

适合您!

(或者:该链接在Fedora 23上对您来说效果很好,但是在Ubuntu 16.04上失败了)

这是因为链接所使用的发行版是未配置其GCC工具链以按需链接共享库的其中一个发行版。

过去,类Unix系统通过不同的规则链接静态库和共享库是正常的。链接序列中的静态库是按示例1中所述按需要进行链接的,但是共享库是无条件链接的。

这种行为在链接时很经济,因为链接器不必考虑程序是否需要共享库:如果它是共享库,则将其链接。大多数链接中的大多数库都是共享库。但是也有缺点:-

  • 运行时是不经济的,因为即使不需要共享库,它也会导致将共享库与程序一起加载。

  • 静态和共享库的不同链接规则可能会使不熟练的程序员感到困惑,他们可能不知道链接中的-lfoo是否会解析为/some/where / libfoo.a /some/where/libfoo.so,可能仍然无法理解共享库和静态库之间的区别。

这种权衡导致了今天的分裂局面。一些发行版已更改了它们对共享库的GCC链接规则,因此按需原则适用于所有库。一些发行版仍然采用旧方法。

即使我同时进行编译和链接,为什么仍会出现此问题?

如果我愿意:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

确保gcc必须首先编译eg1.c,然后将生成的目标文件与libmy_lib.a链接。那么,如何在链接时不知道需要目标文件呢?

因为使用单个命令进行编译和链接不会更改链接顺序的顺序。

运行上面的命令时,gcc指出您需要编译+链接。因此,在幕后,它将生成一个编译命令,然后运行它,然后生成一个链接命令,然后运行它,就像 you 曾经运行过两个命令一样:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

因此,链接失败与 do 运行这两个命令时一样。您在失败中注意到的唯一区别是gcc在compile + link情况下生成了一个临时目标文件,因为您没有告诉它使用eg1.o。我们看到:

/tmp/ccQk1tvs.o: In function `main'

代替:

eg1.o: In function `main':

另请参见

指定相互依赖的链接库的顺序是错误的

以错误的顺序放置相互依存的库只是获得 定义的文件的一种方式,该文件在链接中比提供的文件晚定义。将库放在引用它们的目标文件之前是犯同样错误的另一种方法。

A23:

Your linkage consumes libraries before the object files that refer to them

  • You are trying to compile and link your program with the GCC toolchain.
  • Your linkage specifies all of the necessary libraries and library search paths
  • If libfoo depends on libbar, then your linkage correctly puts libfoo before libbar.
  • Your linkage fails with undefined reference to something errors.
  • But all the undefined somethings are declared in the header files you have #included and are in fact defined in the libraries that you are linking.

Examples are in C. They could equally well be C++

A minimal example involving a static library you built yourself

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

You build your static library:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

You compile your program:

$ gcc -I. -c -o eg1.o eg1.c

You try to link it with libmy_lib.a and fail:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

The same result if you compile and link in one step, like:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

A minimal example involving a shared system library, the compression library libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Compile your program:

$ gcc -c -o eg2.o eg2.c

Try to link your program with libz and fail:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

Same if you compile and link in one go:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

And a variation on example 2 involving pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

What are you doing wrong?

In the sequence of object files and libraries you want to link to make your program, you are placing the libraries before the object files that refer to them. You need to place the libraries after the object files that refer to them.

Link example 1 correctly:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Success:

$ ./eg1 
Hello World

Link example 2 correctly:

$ gcc -o eg2 eg2.o -lz

Success:

$ ./eg2 
1.2.8

Link the example 2 pkg-config variation correctly:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

The explanation

Reading is optional from here on.

By default, a linkage command generated by GCC, on your distro, consumes the files in the linkage from left to right in commandline sequence. When it finds that a file refers to something and does not contain a definition for it, to will search for a definition in files further to the right. If it eventually finds a definition, the reference is resolved. If any references remain unresolved at the end, the linkage fails: the linker does not search backwards.

First, example 1, with static library my_lib.a

A static library is an indexed archive of object files. When the linker finds -lmy_lib in the linkage sequence and figures out that this refers to the static library ./libmy_lib.a, it wants to know whether your program needs any of the object files in libmy_lib.a.

There is only object file in libmy_lib.a, namely my_lib.o, and there's only one thing defined in my_lib.o, namely the function hw.

The linker will decide that your program needs my_lib.o if and only if it already knows that your program refers to hw, in one or more of the object files it has already added to the program, and that none of the object files it has already added contains a definition for hw.

If that is true, then the linker will extract a copy of my_lib.o from the library and add it to your program. Then, your program contains a definition for hw, so its references to hw are resolved.

When you try to link the program like:

$ gcc -o eg1 -L. -lmy_lib eg1.o

the linker has not added eg1.o to the program when it sees -lmy_lib. Because at that point, it has not seen eg1.o. Your program does not yet make any references to hw: it does not yet make any references at all, because all the references it makes are in eg1.o.

So the linker does not add my_lib.o to the program and has no further use for libmy_lib.a.

Next, it finds eg1.o, and adds it to be program. An object file in the linkage sequence is always added to the program. Now, the program makes a reference to hw, and does not contain a definition of hw; but there is nothing left in the linkage sequence that could provide the missing definition. The reference to hw ends up unresolved, and the linkage fails.

Second, example 2, with shared library libz

A shared library isn't an archive of object files or anything like it. It's much more like a program that doesn't have a main function and instead exposes multiple other symbols that it defines, so that other programs can use them at runtime.

Many Linux distros today configure their GCC toolchain so that its language drivers (gcc,g++,gfortran etc) instruct the system linker (ld) to link shared libraries on an as-needed basis. You have got one of those distros.

This means that when the linker finds -lz in the linkage sequence, and figures out that this refers to the shared library (say) /usr/lib/x86_64-linux-gnu/libz.so, it wants to know whether any references that it has added to your program that aren't yet defined have definitions that are exported by libz

If that is true, then the linker will not copy any chunks out of libz and add them to your program; instead, it will just doctor the code of your program so that:-

  • At runtime, the system program loader will load a copy of libz into the same process as your program whenever it loads a copy of your program, to run it.

  • At runtime, whenever your program refers to something that is defined in libz, that reference uses the definition exported by the copy of libz in the same process.

Your program wants to refer to just one thing that has a definition exported by libz, namely the function zlibVersion, which is referred to just once, in eg2.c. If the linker adds that reference to your program, and then finds the definition exported by libz, the reference is resolved

But when you try to link the program like:

gcc -o eg2 -lz eg2.o

the order of events is wrong in just the same way as with example 1. At the point when the linker finds -lz, there are no references to anything in the program: they are all in eg2.o, which has not yet been seen. So the linker decides it has no use for libz. When it reaches eg2.o, adds it to the program, and then has undefined reference to zlibVersion, the linkage sequence is finished; that reference is unresolved, and the linkage fails.

Lastly, the pkg-config variation of example 2 has a now obvious explanation. After shell-expansion:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

becomes:

gcc -o eg2 -lz eg2.o

which is just example 2 again.

I can reproduce the problem in example 1, but not in example 2

The linkage:

gcc -o eg2 -lz eg2.o

works just fine for you!

(Or: That linkage worked fine for you on, say, Fedora 23, but fails on Ubuntu 16.04)

That's because the distro on which the linkage works is one of the ones that does not configure its GCC toolchain to link shared libraries as-needed.

Back in the day, it was normal for unix-like systems to link static and shared libraries by different rules. Static libraries in a linkage sequence were linked on the as-needed basis explained in example 1, but shared libraries were linked unconditionally.

This behaviour is economical at linktime because the linker doesn't have to ponder whether a shared library is needed by the program: if it's a shared library, link it. And most libraries in most linkages are shared libraries. But there are disadvantages too:-

  • It is uneconomical at runtime, because it can cause shared libraries to be loaded along with a program even if doesn't need them.

  • The different linkage rules for static and shared libraries can be confusing to inexpert programmers, who may not know whether -lfoo in their linkage is going to resolve to /some/where/libfoo.a or to /some/where/libfoo.so, and might not understand the difference between shared and static libraries anyway.

This trade-off has led to the schismatic situation today. Some distros have changed their GCC linkage rules for shared libraries so that the as-needed principle applies for all libraries. Some distros have stuck with the old way.

Why do I still get this problem even if I compile-and-link at the same time?

If I just do:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

surely gcc has to compile eg1.c first, and then link the resulting object file with libmy_lib.a. So how can it not know that object file is needed when it's doing the linking?

Because compiling and linking with a single command does not change the order of the linkage sequence.

When you run the command above, gcc figures out that you want compilation + linkage. So behind the scenes, it generates a compilation command, and runs it, then generates a linkage command, and runs it, as if you had run the two commands:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

So the linkage fails just as it does if you do run those two commands. The only difference you notice in the failure is that gcc has generated a temporary object file in the compile + link case, because you're not telling it to use eg1.o. We see:

/tmp/ccQk1tvs.o: In function `main'

instead of:

eg1.o: In function `main':

See also

The order in which interdependent linked libraries are specified is wrong

Putting interdependent libraries in the wrong order is just one way in which you can get files that need definitions of things coming later in the linkage than the files that provide the definitions. Putting libraries before the object files that refer to them is another way of making the same mistake.

回答24:

当您的包含路径不同时

当头文件及其关联的共享库(.lib文件)不同步时,可能会发生链接器错误。让我解释。

链接器如何工作?链接器通过比较函数的签名将函数声明(在标头中声明)与其定义(在共享库中)匹配。如果链接器找不到与之完全匹配的函数定义,则会出现链接器错误。

即使声明和定义似乎匹配,是否仍然可能出现链接器错误?是!它们在源代码中看起来可能相同,但实际上取决于编译器所看到的内容。本质上,您可能会遇到如下情况:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

请注意,尽管两个函数声明在源代码中看起来是一样的,但是根据编译器它们实际上是不同的。

您可能会问,这样的情况最终会如何? 包括路径!如果在编译共享库时,包含路径导致header1.h,而您最终在自己的程序中使用header2.h,那么您将被抓到头文件想知道发生了什么(双关语意)。

下面说明了如何在现实世界中发生这种情况。

进一步举例说明

我有两个项目:graphics.libmain.exe。这两个项目都依赖common_math.h。假设该库导出以下功能:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

然后继续进行操作,并将库包含在您自己的项目中。

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

轰!您会收到一个链接器错误,并且不知道为什么它会失败。原因是公共库使用了相同版本的不同版本,包括common_math.h(我在示例中通过添加其他路径使其变得很明显,但可能并不总是那么明显。也许包含路径在编译器设置中是不同的。

在此示例中,链接器会告诉您找不到draw(),而实际上您知道它显然已由库导出。您可能要花几个小时挠头怀疑问题出在哪里。事实是,链接器看到了不同的签名,因为参数类型略有不同。在示例中,就编译器而言,vec3在两个项目中都是不同的类型。之所以会发生这种情况,是因为它们来自两个略有不同的包含文件(也许包含文件来自两个不同版本的库)。

调试链接器

DUMPBIN是您的朋友,如果您使用的是Visual Studio。我敢肯定其他编译器也有其他类似的工具。

过程如下:

  1. 请注意链接器错误中给出的奇怪的错误名称。 (例如draw @ graphics @ XYZ)。
  2. 将库中导出的符号转储到文本文件中。
  3. 搜索导出的感兴趣的符号,并注意错误的名称是不同的。
  4. 请注意,为什么名称混乱的结果会有所不同。您可以看到参数类型是不同的,即使它们在源代码中看起来相同。
  5. 原因为何不同。在上面给出的示例中,由于包含文件的不同,它们是不同的。

[1]我所说的项目是指一组源文件,这些源文件链接在一起以生成库或可执行文件。

编辑1:重写第一部分以便于理解。请在下面发表评论,让我知道是否需要修复其他问题。谢谢!

A24:

When your include paths are different

Linker errors can happen when a header file and its associated shared library (.lib file) go out of sync. Let me explain.

How do linkers work? The linker matches a function declaration (declared in the header) with its definition (in the shared library) by comparing their signatures. You can get a linker error if the linker doesn't find a function definition that matches perfectly.

Is it possible to still get a linker error even though the declaration and the definition seem to match? Yes! They might look the same in source code, but it really depends on what the compiler sees. Essentially you could end up with a situation like this:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

Note how even though both the function declarations look identical in source code, but they are really different according to the compiler.

You might ask how one ends up in a situation like that? Include paths of course! If when compiling the shared library, the include path leads to header1.h and you end up using header2.h in your own program, you'll be left scratching your header wondering what happened (pun intended).

An example of how this can happen in the real world is explained below.

Further elaboration with an example

I have two projects: graphics.lib and main.exe. Both projects depend on common_math.h. Suppose the library exports the following function:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

And then you go ahead and include the library in your own project.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Boom! You get a linker error and you have no idea why it's failing. The reason is that the common library uses different versions of the same include common_math.h (I have made it obvious here in the example by including a different path, but it might not always be so obvious. Maybe the include path is different in the compiler settings).

Note in this example, the linker would tell you it couldn't find draw(), when in reality you know it obviously is being exported by the library. You could spend hours scratching your head wondering what went wrong. The thing is, the linker sees a different signature because the parameter types are slightly different. In the example, vec3 is a different type in both projects as far as the compiler is concerned. This could happen because they come from two slightly different include files (maybe the include files come from two different versions of the library).

Debugging the linker

DUMPBIN is your friend, if you are using Visual Studio. I'm sure other compilers have other similar tools.

The process goes like this:

  1. Note the weird mangled name given in the linker error. (eg. draw@graphics@XYZ).
  2. Dump the exported symbols from the library into a text file.
  3. Search for the exported symbol of interest, and notice that the mangled name is different.
  4. Pay attention to why the mangled names ended up different. You would be able to see that the parameter types are different, even though they look the same in the source code.
  5. Reason why they are different. In the example given above, they are different because of different include files.

[1] By project I mean a set of source files that are linked together to produce either a library or an executable.

EDIT 1: Rewrote first section to be easier to understand. Please comment below to let me know if something else needs to be fixed. Thanks!

回答25:

不一致的UNICODE定义

Windows UNICODE构建是使用TCHAR等构建的。定义为wchar_t等。当不使用UNICODE构建的定义为< code> TCHAR 定义为char等。这些UNICODE_UNICODE定义会影响所有" T"字符串类型LPTSTRLPCTSTR及其麋鹿。

建立一个定义了UNICODE的库,并尝试在未定义UNICODE的项目中链接它会导致链接器错误,因为定义不匹配的TCHARcharwchar_t

错误通常包括一个函数,该函数的值具有charwchar_t派生类型,这些值可能包括std::basic_string<>等以及。在代码中浏览受影响的功能时,通常会引用TCHARstd::basic_string 等。这是一个说明迹象该代码最初是为UNICODE和多字节字符(或"窄"字符)构建的。

要解决此问题,请使用统一的UNICODE(和_UNICODE)定义来构建所有必需的库和项目。

  1. 这可以通过以下任一方式完成:

    #define UNICODE
    #define _UNICODE
    
  2. 或在项目设置中;

    项目属性>常规>项目默认设置>字符集

  3. 或在命令行上

    /DUNICODE /D_UNICODE
    

如果不打算使用UNICODE,请确保未设置定义,并且/或者在项目中使用了多字符设置并且一致地应用了该替代方法。

不要忘记在"发行版"和"调试版"之间保持一致。

A25:

Inconsistent UNICODE definitions

A Windows UNICODE build is built with TCHAR etc. being defined as wchar_t etc. When not building with UNICODE defined as build with TCHAR defined as char etc. These UNICODE and _UNICODE defines affect all the "T" string types; LPTSTR, LPCTSTR and their elk.

Building one library with UNICODE defined and attempting to link it in a project where UNICODE is not defined will result in linker errors since there will be a mismatch in the definition of TCHAR; char vs. wchar_t.

The error usually includes a function a value with a char or wchar_t derived type, these could include std::basic_string<> etc. as well. When browsing through the affected function in the code, there will often be a reference to TCHAR or std::basic_string<TCHAR> etc. This is a tell-tale sign that the code was originally intended for both a UNICODE and a Multi-Byte Character (or "narrow") build.

To correct this, build all the required libraries and projects with a consistent definition of UNICODE (and _UNICODE).

  1. This can be done with either;

    #define UNICODE
    #define _UNICODE
    
  2. Or in the project settings;

    Project Properties > General > Project Defaults > Character Set

  3. Or on the command line;

    /DUNICODE /D_UNICODE
    

The alternative is applicable as well, if UNICODE is not intended to be used, make sure the defines are not set, and/or the multi-character setting is used in the projects and consistently applied.

Do not forget to be consistent between the "Release" and "Debug" builds as well.

回答26:

清理并重建

"干净"的构建可以清除以前的构建,失败的构建,不完整的构建以及其他与构建系统相关的构建问题中可能留下的"死木"。

通常,IDE或内部版本将包含某种形式的"清除"功能,但此功能可能未正确配置(例如,在手动makefile中)或可能失败(例如,中间或结果二进制文件为只读)。 / p>

"清理"完成后,请确认"清理"成功,并且已成功删除所有生成的中间文件(例如,自动生成文件)。

这个过程可以看作是最后的手段,但是通常是一个很好的第一步;尤其是如果最近已添加了与错误相关的代码(在本地或从源存储库添加)。

A26:

Clean and rebuild

A "clean" of the build can remove the "dead wood" that may be left lying around from previous builds, failed builds, incomplete builds and other build system related build issues.

In general the IDE or build will include some form of "clean" function, but this may not be correctly configured (e.g. in a manual makefile) or may fail (e.g. the intermediate or resultant binaries are read-only).

Once the "clean" has completed, verify that the "clean" has succeeded and all the generated intermediate file (e.g. an automated makefile) have been successfully removed.

This process can be seen as a final resort, but is often a good first step; especially if the code related to the error has recently been added (either locally or from the source repository).

回答27:

const变量声明/定义中缺少" extern"(仅C ++)

对于使用C语言的人来说,在C ++中,全局const变量具有内部(或静态)链接可能会令人惊讶。在C语言中,情况并非如此,因为所有全局变量都是隐式地extern(即缺少static关键字时)。

示例:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

正确的方法是使用头文件并将其包含在file2.cpp file1.cpp

extern const int test;
extern int test2;

或者可以使用显式的extern

在file1.cpp中声明const变量

A27:

Missing "extern" in const variable declarations/definitions (C++ only)

For people coming from C it might be a surprise that in C++ global constvariables have internal (or static) linkage. In C this was not the case, as all global variables are implicitly extern (i.e. when the static keyword is missing).

Example:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

correct would be to use a header file and include it in file2.cpp and file1.cpp

extern const int test;
extern int test2;

Alternatively one could declare the const variable in file1.cpp with explicit extern

回答28:

尽管这是一个很老的问题,有多个被接受的答案,但我还是想分享如何解决晦涩的"未定义的引用"错误。

库的不同版本

我使用别名来引用std::filesystem::path:自C ++ 17起,文件系统就位于标准库中,但我的程序需要也要在C +中编译+14 ,所以我决定使用变量别名:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif

假设我有三个文件:main.cpp,file.h,file.cpp:

  • file.h #include的<< strong> experimental :: filesystem >,并包含上面的代码
  • file.cpp ,file.h的实现,#include的" file.h "
  • main.cpp #include的<< strong>文件系统>和" file.h "

请注意main.cpp和file.h中使用的不同库。由于main.cpp在文件系统之后#include了" file.h ",因此所使用的文件系统版本为 C ++ 17版本 。我以前使用以下命令来编译程序:

$ g++-g-std=c++17-cmain.cpp->将main.cpp编译为main.o
$ g++-g-std=c++ 17 -c file.cpp ->将file.cpp和file.h编译为file.o
$ g++-g-std=c++17-o可执行main.o文件.o -lstdc ++ fs ->链接main.o和file.o

通过这种方式,包含在file.o中并且在main.o中使用的任何函数需要path_t 给出"未定义的引用"错误,因为< strong> main.o 是指 std::filesystem::path ,而 file.o 是指 std :: experimental :: filesystem :: path

解决方案

要解决此问题,我只需要将file.h中的 更改为

A28:

Even though this is a pretty old questions with multiple accepted answers, I'd like to share how to resolve an obscure "undefined reference to" error.

Different versions of libraries

I was using an alias to refer to std::filesystem::path: filesystem is in the standard library since C++17 but my program needed to also compile in C++14 so I decided to use a variable alias:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif

Let's say I have three files: main.cpp, file.h, file.cpp:

  • file.h #include's <experimental::filesystem> and contains the code above
  • file.cpp, the implementation of file.h, #include's "file.h"
  • main.cpp #include's <filesystem> and "file.h"

Note the different libraries used in main.cpp and file.h. Since main.cpp #include'd "file.h" after <filesystem>, the version of filesystem used there was the C++17 one. I used to compile the program with the following commands:

$ g++ -g -std=c++17 -c main.cpp -> compiles main.cpp to main.o
$ g++ -g -std=c++17 -c file.cpp -> compiles file.cpp and file.h to file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs -> links main.o and file.o

This way any function contained in file.o and used in main.o that required path_t gave "undefined reference" errors because main.o referred to std::filesystem::path but file.o to std::experimental::filesystem::path.

Resolution

To fix this I just needed to change <experimental::filesystem> in file.h to <filesystem>.

回答29:

链接共享库时,请确保未隐藏使用的符号。

gcc的默认行为是所有符号都是可见的。但是,当使用选项-fvisibility=hidden构建翻译单元时,只有标记为__attribute__((visibility("default")))的功能/符号在外部。结果共享对象。

您可以通过调用以下命令检查所寻找的符号是否为外部符号:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

隐藏的/本地符号由nm显示,具有小写的符号类型,例如t而不是`T代表代码段:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

您还可以将nm与选项-C一起使用来对名称进行解密(如果使用C ++)。

类似于Windows-dll,可以使用定义标记公共功能,例如,将DLL_PUBLIC定义为:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

大致对应于Windows的/ MSVC版本:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

更多有关可见性的信息可在gcc Wiki上找到。


当使用-fvisibility=hidden编译翻译单元时,生成的符号仍具有外部链接(通过nm以大写符号类型显示),可用于如果目标文件成为静态库的一部分,则外部链接没有问题。仅当将目标文件链接到共享库时,链接才成为本地链接。

要查找目标文件中隐藏的符号,请运行:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

A29:

When linking against shared libraries, make sure that the used symbols are not hidden.

The default behavior of gcc is that all symbols are visible. However, when the translation units are built with option -fvisibility=hidden, only functions/symbols marked with __attribute__ ((visibility ("default"))) are external in the resulting shared object.

You can check whether the symbols your are looking for are external by invoking:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

the hidden/local symbols are shown by nm with lowercase symbol type, for example t instead of `T for code-section:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

You can also use nm with the option -C to demangle the names (if C++ was used).

Similar to Windows-dlls, one would mark public functions with a define, for example DLL_PUBLIC defined as:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

Which roughly corresponds to Windows'/MSVC-version:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

More information about visibility can be found on the gcc wiki.


When a translation unit is compiled with -fvisibility=hidden the resulting symbols have still external linkage (shown with upper case symbol type by nm) and can be used for external linkage without problem if the object files become part of a static libraries. The linkage becomes local only when the object files are linked into a shared library.

To find which symbols in an object file are hidden run:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

回答30:

不同的体系结构

您可能会看到类似这样的消息:

library machine type 'x64' conflicts with target machine type 'X86'

在这种情况下,这意味着可用的符号用于与要编译的符号不同的体系结构。

在Visual Studio上,这是由于错误的"平台"造成的,您需要选择正确的库或安装正确的库版本。

在Linux上,可能是由于库文件夹错误(例如,使用lib而不是lib64)。

在MacOS上,可以选择将两个体系结构都存储在同一文件中。链接可能希望这两个版本都存在,但只有一个存在。使用库的错误lib / lib64文件夹也可能是一个问题。

A30:

Different architectures

You may see a message like:

library machine type 'x64' conflicts with target machine type 'X86'

In that case, it means that the available symbols are for a different architecture than the one you are compiling for.

On Visual Studio, this is due to the wrong "Platform", and you need to either select the proper one or install the proper version of the library.

On Linux, it may be due to the wrong library folder (using lib instead of lib64 for instance).

On MacOS, there is the option of shipping both architectures in the same file. It may be that the link expects both versions to be there, but only one is. It can also be an issue with the wrong lib/lib64 folder where the library is picked up.

回到顶部