programing

정의되지 않은 참조/해결되지 않은 외부 기호 오류는 무엇이며 어떻게 수정합니까?

mailnote 2023. 10. 28. 08:07
반응형

정의되지 않은 참조/해결되지 않은 외부 기호 오류는 무엇이며 어떻게 수정합니까?

정의되지 않은 참조/해결되지 않은 외부 기호 오류란 무엇입니까?일반적인 원인과 해결/예방 방법은 무엇입니까?

C++ 프로그램의 컴파일은 2.2에서 지정한 대로 몇 단계로 이루어집니다(참고를 위해 Keith Thompson에게 크레딧 제공).

번역의 구문 규칙 중 우선 순위는 다음 단계로 지정됩니다 [각주 참조].

  1. 물리적 소스 파일 문자는 필요한 경우 구현 정의 방식으로 기본 소스 문자 집합에 매핑됩니다(끝줄 표시기에 새 줄 문자 도입).[SNIP]
  2. 백슬래시 문자(\)의 각 인스턴스 다음에 새 줄 문자가 추가되면 삭제되고 물리적 소스 라인을 연결하여 논리적 소스 라인을 형성합니다.[SNIP]
  3. 소스 파일은 전처리 토큰(2.5)과 공백 문자 시퀀스(댓글 포함)로 분해됩니다.[SNIP]
  4. 전처리 지시를 실행하고 매크로 호출을 확장하며 _Pragmonary 연산자 식을 실행합니다.[SNIP]
  5. 문자 리터럴 또는 문자열 리터럴의 각 소스 문자 집합 멤버와 문자 리터럴 또는 원시 문자열 리터럴의 각 이스케이프 시퀀스 및 범용 문자 이름이 실행 문자 집합의 해당 멤버로 변환됩니다. [SNIP]
  6. 인접 문자열 리터럴 토큰이 연결됩니다.
  7. 토큰을 구분하는 공백 문자는 더 이상 중요하지 않습니다.각 전처리 토큰은 토큰으로 변환됩니다(2.7).결과 토큰은 통사적, 의미적으로 분석되고 번역 단위로 번역됩니다.[SNIP]
  8. 번역된 번역 단위와 인스턴스화 단위의 조합은 다음과 같습니다. [SNIP]
  9. 모든 외부 엔티티 참조가 해결되었습니다. 라이브러리 구성 요소는 현재 변환에 정의되지 않은 엔티티에 대한 외부 참조를 충족하도록 연결됩니다. 모든 번역기 출력은 실행 환경에서 실행에 필요한 정보를 포함하는 프로그램 이미지로 수집됩니다.(emphasis 광산)

[각주] 구현은 실제로는 서로 다른 단계가 함께 접힐 수 있지만, 이러한 개별 단계가 발생하는 것처럼 행동해야 합니다.

지정된 오류는 이 마지막 컴파일 단계에서 발생하며, 일반적으로 링크라고 합니다.기본적으로 여러 구현 파일을 개체 파일이나 라이브러리에 컴파일하고 이제는 이 파일들을 함께 작동시키길 원한다는 의미입니다.

기호를 정의했다고 가정합니다.a인에a.cpp.지금이다,b.cpp 그 상징을 선언하고 사용했습니다.연결하기 전에 단순히 해당 기호가 어딘가에 정의되었다고 가정할 뿐 아직 어디에 있는지는 중요하지 않습니다.연결 단계는 기호를 찾아 올바르게 연결하는 역할을 합니다.b.cpp(실제로는 이를 사용하는 개체나 라이브러리에).

Visual Studio를 .lib파일에는 내보낸 기호와 가져온 기호의 표가 들어 있습니다.가져온 기호는 사용자가 링크하는 라이브러리에 대해 확인되고 내보낸 기호는 다음을 사용하는 라이브러리에 대해 제공됩니다..lib(있는 경우)

다른 컴파일러/플랫폼에도 유사한 메커니즘이 있습니다.

일반적인 오류 메시지는error LNK2001,error LNK1120,error LNK2019마이크로소프트 Visual Studio와.undefined reference to symbolGCC의 이름입니다.

코드:

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

일반적인 원인은 다음과 같습니다.

클래스 구성원:

순수한virtualdestructor는 구현이 필요합니다.

디스트럭터 퓨어를 선언하려면 일반 함수와 달리 정의해야 합니다.

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

이는 개체가 암시적으로 파괴될 때 기본 클래스 파괴자가 호출되므로 정의가 필요합니다.

virtual메소드는 구현되거나 순수한 것으로 정의되어야 합니다.

이것은 비-와 유사합니다.virtual함수를 사용하지 않고 링크러 오류가 발생할 수도 있다는 추론이 추가되어 정의가 없는 메서드입니다.

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데이터 구성원.

적절한 라이브러리/개체 파일에 대한 링크 또는 구현 파일 컴파일 실패

일반적으로 각 변환 단위는 해당 변환 단위에 정의된 기호의 정의를 포함하는 개체 파일을 생성합니다.이러한 기호를 사용하려면 해당 개체 파일에 대해 링크해야 합니다.

gcc에서는 명령줄에서 링크할 모든 개체 파일을 지정하거나 구현 파일을 함께 컴파일합니다.

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

-l...오른쪽에 있어야 합니다..o/.c/.cpp서류철

libraryName여기에는 플랫폼별 추가 없이 라이브러리의 맨 이름만 나와 있습니다.그래서 예를 들어 리눅스 라이브러리 파일은 보통 다음과 같이 불립니다.libfoo.so하지만 당신은 글만 쓰잖아요-lfoo. Windows에서 동일한 파일을 호출할 수 있습니다.foo.lib, 하지만 당신은 같은 주장을 할 겁니다.파일을 찾을 수 있는 디렉터리를 추가해야 할 수도 있습니다.-L‹directory›. 뒤에 띄어쓰기를 하지 않도록 하십시오.-l아니면-L.

Xcode의 경우:사용자 헤더 검색 경로 추가 -> 라이브러리 검색 경로 추가 -> 실제 라이브러리 참조를 프로젝트 폴더로 끌어다 놓습니다.

MSVS 하에서 프로젝트에 추가된 파일들은 자동적으로 객체 파일들을 함께 연결하고 a.lib일반적으로 파일이 생성됩니다.별도의 프로젝트에서 기호를 사용하려면 다음을 포함해야 합니다.lib프로젝트 설정에 있는 파일을(를)이 작업은 프로젝트 속성의 Linker 섹션에서 수행됩니다.Input -> Additional Dependencies(로)lib파일을 추가해야 합니다.Linker -> General -> Additional Library Directories) 가 제공된 타사 라이브러리를 사용하는 경우lib파일, 그렇지 않으면 일반적으로 오류가 발생합니다.

컴파일에 파일을 추가하는 것을 잊어버릴 수도 있습니다. 이 경우 개체 파일이 생성되지 않습니다.gcc에서는 명령줄에 파일을 추가합니다.MSVS에서 프로젝트에 파일을 추가하면 파일이 자동으로 컴파일됩니다(파일은 수동으로 빌드에서 개별적으로 제외될 수 있음).

Windows 프로그래밍에서 필요한 라이브러리를 연결하지 않았다는 표시는 해결되지 않은 기호의 이름이 다음으로 시작된다는 것입니다.__imp_해야 할 설명서에서 함수 이름을 찾아보면 사용해야 할 라이브러리가 표시됩니다.예를 들어, MSDN은 각 기능의 하단에 있는 "라이브러리"라는 섹션에 정보를 상자에 넣습니다.

선언되었지만 변수 또는 함수를 정의하지 않았습니다.

일반적인 변수 선언은

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-qualifier가 있을 수 있습니다.

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)
                          

불일치의 다른 예는 다음과 같습니다.

  • 한 네임스페이스에 선언된 함수/변수, 다른 네임스페이스에 정의된 함수입니다.
  • 클래스 멤버로 선언된 함수/변수, 글로벌(또는 그 반대)로 정의됩니다.
  • 함수 반환 형식, 매개 변수 번호 및 유형, 호출 규칙이 모두 정확하게 일치하지는 않습니다.

컴파일러로부터의 오류 메시지는 종종 선언되었지만 결코 정의되지 않았던 변수 또는 함수의 완전한 선언을 제공합니다.당신이 제시한 정의와 밀접하게 비교해 보세요.모든 세부 사항이 일치하는지 확인합니다.

상호 종속적인 링크된 라이브러리가 지정된 순서가 잘못되었습니다.

라이브러리가 연결되는 순서는 라이브러리가 서로 의존하는지 여부가 중요합니다.일반적으로 if libraryA도서관에 따라 다릅니다.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

다시 한 번 반복하자면, 순서가 중요합니다!

기호들은 C 프로그램에서 정의되었고 C++ 코드에서 사용되었습니다.

함수(또는 변수)void foo()C 프로그램에서 정의되었으며 C++ 프로그램에서 사용을 시도합니다.

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

C++ 링커는 이름이 망가지기를 기대하므로 다음과 같이 함수를 선언해야 합니다.

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

이와 동등하게, 함수(또는 변수)는 C 프로그램에서 정의되는 대신void foo()C++로 정의되었으나 C 연결을 사용했습니다.

extern "C" void foo();

그리고 C++ 링크가 있는 C++ 프로그램에서 사용하려고 합니다.

만약 전체 라이브러리가 헤더 파일에 포함되어 있다면(C 코드로 컴파일된 경우), 포함은 다음과 같이 이루어져야 합니다.

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

"정의되지 않은 참조/unresolved 외부 기호"란 무엇입니까?

"정의되지 않은 참조/해결되지 않은 외부 기호"란 무엇인지 설명해 보겠습니다.

참고: 저는 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

OK(오류 없음), 객체 파일만 구축하기 때문에 링크가 아직 완료되지 않았습니다.링크 시도

$ 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에 대한 레이블이 없는 것을 확인했습니다. 그래서 linker가 찾지 못했습니다.하지만 우리는 해커입니다 :) 그리고 우리는 그것을 고칠 수 있습니다.텍스트 편집기에서 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

ok, redelf 출력 참조(symbols)

$ 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

좋아요, 해킹합니다 :)

따라서 결과적으로 링커가 개체 파일에서 전역 기호를 찾을 수 없는 경우 "정의되지 않은 참조/해결되지 않은 외부 기호 오류"가 발생합니다.

다른 모든 것이 실패하면 다시 컴파일합니다.

최근에 Visual Studio 2012에서 문제가 된 파일을 다시 컴파일하는 것만으로 해결되지 않은 외부 오류를 제거할 수 있었습니다.다시 만들었을 때 오류가 사라졌습니다.

이 문제는 일반적으로 두 개 이상의 라이브러리가 순환 종속성을 가질 때 발생합니다.라이브러리 A는 B.lib에서 기호를 사용하려고 시도하고 라이브러리 B는 A.lib에서 기호를 사용하려고 시도합니다.처음부터 둘 다 존재하지 않습니다.A를 컴파일하려고 하면 B.lib을 찾을 수 없기 때문에 링크 단계가 실패합니다.A.lib이 생성되지만 dll은 생성되지 않습니다.그런 다음 B를 컴파일하면 B.lib이 생성됩니다.이제 B.lib이 발견되었기 때문에 A를 다시 컴파일하는 것이 가능합니다.

템플릿 구현이 보이지 않습니다.

특수화되지 않은 템플릿은 해당 템플릿을 사용하는 모든 변환 단위에서 정의를 볼 수 있어야 합니다.즉, 템플릿의 정의를 구현 파일로 분리할 수 없습니다.구현을 분리해야 하는 경우 일반적인 해결 방법은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헤더 파일 또는 이 파일을 사용하는 변환 장치에 보이는 어떤 장소로.

특수화된 템플릿은 구현 파일에서 구현할 수 있으며 구현을 볼 필요는 없지만 특수화는 미리 선언해야 합니다.

자세한 설명과 가능한 다른 해결 방법(명시적 인스턴스화)은 이 질문과 답변을 참조하십시오.

이것은 모든 VC++ 프로그래머들이 몇 번이고 다시 보게 되는 가장 혼란스러운 오류 메시지 중 하나입니다.먼저 상황을 명확히 해 두자.

A. 기호가 무엇입니까?간단히 말해서, 기호는 이름입니다.변수 이름, 함수 이름, 클래스 이름, typeef 이름 또는 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에서 function foo를 부르고 싶어서 추가합니다.

void foo();

function fo (),를 선언하고 다른 function body에서 호출하는 것, 예를 들어,bar():

void bar()
{
    foo();
}

이제 이 코드를 만들 때 foo가 해결되지 않은 기호라고 불평하는 LNK2019 오류가 발생합니다.이 경우, foo()는 a.cpp에 정의가 있지만, 우리가 부르는 것과는 다릅니다(다른 반환 값).이것은 정의가 존재하는 경우입니다.

  1. 정의가존재하지않습니다

라이브러리의 일부 함수를 호출하려고 하지만 가져오기 라이브러리가 추가 종속성 목록에 추가되지 않는 경우(설정 위치:Project | Properties | Configuration Properties | Linker | Input | Additional Dependency당신의 프로젝트 설정의.현재 검색 범위에 정의가 존재하지 않으므로 링커에서 LNK2019를 보고합니다.

modules/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 참조). 1 참조).

클래스 가져오기/내보내기를 유사하게 수행할 수 있습니다.

class DLLIMPEXP X
{
};

정의되지 않은 참조 또는 유사 'unusual' 진입점 참조(특히 시각적 studio의 경우).

실제 IDE로 올바른 프로젝트 유형을 선택하지 못했을 수도 있습니다.IDE는 예를 들어 바인딩을 원할 수 있습니다.Windows Application은 일반적으로 사용되는 대신 위의 누락된 참조에서 지정한 대로 해당 진입점 기능으로 프로젝트를 수행합니다.int main(int argc, char** argv);서명.

IDE에서 일반 콘솔 프로젝트를 지원하는 경우 Windows 응용 프로그램 프로젝트 대신 이 프로젝트 유형을 선택할 수 있습니다.


여기 실제 문제에서 좀 더 자세히 다루는 case1case2가 있습니다.

또한 타사 라이브러리를 사용하는 경우 올바른 32/64비트 이진 파일이 있는지 확인합니다.

마이크로소프트는 다음을 제공합니다.#pragma링크 시간에 정확한 라이브러리를 참조합니다.

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

라이브러리의 디렉토리를 포함하는 라이브러리 경로 외에 라이브러리의 전체 이름이 되어야 합니다.

Visual Studio NuGet 패키지를 새 도구 집합 버전으로 업데이트해야 합니다.

방금 libpng를 Visual Studio 2013과 연결하려고 하는데 문제가 생겼습니다.문제는 패키지 파일에 Visual Studio 2010 및 2012용 라이브러리만 있다는 것입니다.

올바른 해결책은 개발자가 업데이트된 패키지를 출시한 후 업그레이드하기를 희망하는 것이지만 VS2013의 추가 설정에서 VS2012 라이브러리 파일을 가리키며 해킹을 함으로써 효과가 있었습니다.

패키지를 편집했습니다(에서packages in s 에서 folder를 folder in the solution's directory) 를 선택합니다.packagename\build\native\packagename.targets그리고 그 파일 안에, 모든 것을 복사하는 것.v110절편으로제가 바꿨습니다.v110.v120조건 필드에서는 파일 이름 경로를 모두 다음과 같이 남겨두기 위해 매우 주의해야 합니다.v110를 통해 할 수 이 효과가 이를 통해 Visual Studio 2013은 2012년 라이브러리에 연결할 수 있었고, 이 경우에는 효과가 있었습니다.

1,000개의 .cpp 파일과 1,000개의 .h 파일이 있는 c++로 작성된 큰 프로젝트가 있다고 가정합니다.그리고 프로젝트가 또한 10개의 정적 라이브러리에 의존한다고 가정해 보겠습니다.우리가 Windows를 사용하고 있으며 Visual Studio 20xx에서 프로젝트를 구축하고 있다고 가정해 보겠습니다.Ctrl + F7 Visual Studio를 누르면 전체 솔루션의 컴파일이 시작됩니다(솔루션에 프로젝트가 하나만 있다고 가정합니다).

편찬의 의미가 무엇입니까?

  • Visual Studio는 .vcxproj 파일을 검색하고 확장자가 .cpp인 각 파일을 컴파일하기 시작합니다.컴파일순서가정의되지않았습니다.따라서 main.cpp 파일이 먼저 컴파일되었다고 가정해서는 안 됩니다.
  • .cpp 파일이 .cpp 파일에 정의되거나 정의되지 않을 수 있는 기호를 찾기 위해 추가 .h 파일에 의존하는 경우
  • 컴파일러가 하나의 심볼을 찾을 수 없는 .cpp 파일이 하나 존재하는 경우 컴파일러 시간 오류로 인해 심볼 x를 찾을없음 메시지가 표시됩니다.
  • 확장명이 .cpp인 각 파일에 대해 개체 파일 .o가 생성되며 Visual Studio에서도 ProjectName이라는 파일에 출력을 씁니다.깨끗합니다.linker에서 처리해야 하는 모든 개체 파일을 포함하는 txt.

두 번째 컴파일 단계는 링커가 수행합니다.링커는 모든 개체 파일을 병합하고 최종적으로 출력(실행 파일 또는 라이브러리일 수 있음)을 구축해야 합니다.

프로젝트 연결 단계

  • 모든 개체 파일을 구문 분석하여 헤더에서만 선언된 정의를 찾습니다(예: 앞의 답변에서 언급한 클래스의 한 가지 메서드의 코드 또는 클래스 내부의 멤버인 정적 변수의 초기화 이벤트).
  • 개체 파일에서 하나의 기호를 찾을 수 없는 경우 추가 라이브러리에서도 검색됩니다.프로젝트 구성 속성에 새 라이브러리를 추가하는 경우 -> VC++ 디렉터리 -> 라이브러리 디렉터리를 검색하는 폴더를 추가하고 라이브러리 이름을 지정하는 경우 -> Linker -> 입력 - 하나에 작성한 기호를 찾을 수 없는 경우.그는 다음과 같이 들릴 수 있는 링커 시간 오류를 제기합니다.error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

관찰

  1. 링커가 한 개의 기호를 찾으면 다른 도서관에서 찾아보지 않습니다.
  2. 도서관을 연결하는 순서가 중요합니다.
  3. 링커가 하나의 정적 라이브러리에서 외부 기호를 찾으면 프로젝트의 출력에 해당 기호를 포함합니다.그러나 라이브러리가 공유된 경우(동적) 출력에 코드(기호)를 포함하지 않지만 Run-Time 충돌이 발생할 수 있습니다.

이러한 오류를 해결하는 방법

컴파일러 시간 오류:

  • c++ 프로젝트 구문을 정확하게 작성해야 합니다.

링커 시간 오류

  • 헤더 파일에 선언하는 기호를 모두 정의합니다.
  • 사용하다#pragma once컴파일되는 현재 .cpp에 이미 포함되어 있는 경우 컴파일러가 하나의 헤더를 포함하지 않도록 허용하는 것.
  • 외부 라이브러리에 헤더 파일에 정의한 다른 기호와 충돌할 수 있는 기호가 포함되어 있지 않은지 확인합니다.
  • 템플릿을 사용하여 컴파일러가 인스턴스화에 적합한 코드를 생성할 수 있도록 헤더 파일에 각 템플릿 함수의 정의를 포함시키는지 확인할 때.

링커를 사용하여 오류를 진단합니다.

대부분의 현대적인 링커는 다양한 정도로 출력하는 장황한 옵션을 포함합니다.

  • 링크 호출(명령줄),
  • 링크 단계에 어떤 라이브러리가 포함되는지에 대한 데이터,
  • 도서관 위치,
  • 사용된 검색 경로.

gcc 및 clang의 경우 일반적으로 다음을 추가합니다.-v -Wl,--verbose아니면-v -Wl,-v명령 줄로 말입니다.자세한 내용은 여기에서 확인할 수 있습니다.

MSVC 의의 ,/VERBOSE 히)/VERBOSE:LIB이 링크 명령줄에 추가됩니다.

컴파일러/IDE의 버그

최근에 이런 문제가 있었는데 알고보니 비주얼 스튜디오 익스프레스 2013의 버그였습니다.버그를 극복하기 위해 프로젝트에서 소스 파일을 제거하고 다시 추가해야 했습니다.

컴파일러/IDE의 버그일 수 있다고 판단되는 경우 시도할 단계:

  • 프로젝트 지우기(일부 IDE는 이 작업을 수행할 수 있는 옵션이 있으며 개체 파일을 삭제하여 수동으로 수행할 수도 있습니다.
  • 원래 소스 코드에서 모든 소스 코드를 복사하여 새 프로젝트를 시작해 보십시오.

링크된 .lib 파일이 .dll에 연결되어 있습니다.

저도 같은 문제가 있었습니다.프로젝트가 있다고 가정해 보겠습니다. My Project My Project and Test Project.저는 테스트 프로젝트에 마이 프로젝트의 lib 파일을 효과적으로 연결했습니다.그러나 이 lib 파일은 MyProject의 DLL이 구축되면서 생성되었습니다.또한 MyProject의 모든 메서드에 대한 소스 코드를 포함하지 않고 DLL의 진입 지점에만 액세스할 수 있습니다.

이 문제를 해결하기 위해 마이프로젝트를 LIB로 구축하고 테스트프로젝트를 이 .lib 파일에 연결했습니다(생성된 .lib 파일을 TestProject 폴더에 복사합니다).그런 다음 내 프로젝트를 DLL로 다시 빌드할 수 있습니다.TestProject가 연결된 lib에 MyProject의 클래스에 있는 모든 메서드에 대한 코드가 포함되어 있으므로 컴파일하고 있습니다.

링커 오류에 관해서는 사람들이 이 질문을 하는 것 같기 때문에 여기에 이것을 추가할 것입니다.

GCC 5.2.0에서 링커 오류가 발생하는 한 가지 가능한 이유는 새 libstdc++ 라이브러리 ABI가 기본적으로 선택되었기 때문입니다.

std의 유형과 관련된 기호에 대한 정의되지 않은 참조에 대한 링커 오류가 발생하는 경우::__cxx11 네임스페이스 또는 [abi:cxx11] 태그는 _GLIBCXX_USE_CXX11_ABI 매크로에 대해 서로 다른 값으로 컴파일된 개체 파일을 함께 링크하려고 하는 것일 수 있습니다.이는 이전 버전의 GCC로 컴파일된 타사 라이브러리에 연결할 때 일반적으로 발생합니다.서드파티 라이브러리를 새 ABI로 재구성할 수 없는 경우 코드를 이전 ABI로 다시 컴파일해야 합니다.

따라서 5.1.0 이후 GCC로 전환할 때 갑자기 링커 오류가 발생하면 확인해야 할 사항입니다.

링크는 라이브러리를 참조하는 개체 파일보다 먼저 라이브러리를 사용합니다.

  • 프로그램을 컴파일하고 GCC 툴체인과 연결하려고 합니다.
  • 필요한 모든 라이브러리 및 라이브러리 검색 경로가 연결되어 있습니다.
  • 한다면libfoo에 달려 있습니다libbar, 그러면 당신의 연결고리는 정확하게libfoo전에libbar.
  • 다음과 연결이 실패합니다.undefined reference to 뭔가 착오가 있습니다
  • 하지만 정의되지 않은 모든 것들은 당신이 가지고 있는 헤더 파일에 선언됩니다.#include그리고 실제로 링크하는 라이브러리에 정의되어 있습니다.

예는 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

예1.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

예2.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 2를 합니다.pkg-config정확한 변동:

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

설명을

앞으로 독서는 선택사항입니다.

기본적으로, GCC에 의해 생성된 연결 명령어는 명령행 순서대로 왼쪽에서 오른쪽으로 연결된 파일을 소비합니다.파일이 무언가를 참조하고 정의를 포함하지 않는 것을 발견하면 오른쪽에 있는 파일에서 정의를 검색합니다.결국 정의를 찾으면 참조가 해결됩니다.참조가 마지막에 해결되지 않은 상태로 남아 있으면 연결이 실패합니다. 연결기는 뒤로 검색하지 않습니다.

먼저 정적 라이브러리를 사용한 예제 1my_lib.a

정적 라이브러리는 개체 파일의 색인화된 보관 파일입니다.링커가 찾을 때-lmy_lib연결 순서와 수치에서 이것이 정적 라이브러리를 가리킨다는 것을 알 수 있습니다../libmy_lib.a, 그것은 당신의 프로그램이 어떤 객체 파일을 필요로 하는지 알고 싶어합니다.libmy_lib.a.

에 개체 파일만 있습니다.libmy_lib.a,my_lib.o, 그리고 오직 한가지 정의된 것은my_lib.o, , , hw.

링커가 당신의 프로그램이 필요하다고 결정할 것입니다.my_lib.o당신의 프로그램이 다음을 언급하는 것을 이미 알고 있는 경우에만.hw, 이미 프로그램에 추가한 하나 이상의 개체 파일에 이미 추가한 개체 파일 중에 정의를 포함하는 개체 파일이 없습니다.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결국 해결되지 않고 링크가 실패합니다.

둘째, 공유 라이브러리를 사용한 예제 2libz

공유 라이브러리는 개체 파일 등의 아카이브가 아닙니다.그것은 프로그램에 훨씬 더 가깝습니다.main기능하고 대신 다른 프로그램이 실행 시 사용할 수 있도록 정의된 여러 개의 다른 기호를 노출합니다.

오늘날 많은 리눅스 배포자들이 GCC 툴체인을 구성하여 언어 드라이버를 구성합니다.gcc,g++,gfortran등) 시스템 링커에 지시합니다(ld필요에 따라 공유 라이브러리를 연결합니다.당신은 그 디스트로들 중 하나를 가지고 있습니다.

이것은 링커가 발견했을 때-lz링크 시퀀스에서, 그리고 이것이 공유 라이브러리를 지칭한다는 것을 파악합니다(예:)./usr/lib/x86_64-linux-gnu/libz.so, 프로그램에 추가된 참조 중 아직 정의되지 않은 참조가 다음에 의해 내보내는 정의를 가지고 있는지 여부를 알고 싶습니다.libz

만약 그것이 사실이라면, 링커는 어떤 청크도 복사하지 않을 것입니다.libz에 추가합니다.과 같이 합니다. 대신 프로그램의 코드를 다음과 같이 수정합니다.

  • 런타임에 시스템 프로그램 로더가 다음의 복사본을 로드합니다.libz프로그램을 실행하기 위해 프로그램의 복사본을 로드할 때마다 프로그램과 동일한 프로세스로 이동합니다.

  • 런타임에 프로그램이 정의된 내용을 참조할 때마다libz, 해당 참조는 복사본에 의해 내보낸 정의를 사용합니다.libz같은 과정에서

프로그램에서 내보낸 정의가 있는 한 가지만 참조하려고 합니다.libz, , , zlibVersion, 한 번만 언급하면,eg2.c. 링커가 프로그램에 해당 참조를 추가한 다음 내보낸 정의를 찾는 경우libz, 참조가 해결되었습니다.

그러나 프로그램을 연결하려고 하면 다음과 같습니다.

gcc -o eg2 -lz eg2.o

예 1과 같은 방식으로 사건의 순서가 틀렸습니다.링커가 발견한 지점에서-lz, 프로그램에 어떤 것에 대해서도 언급이 없습니다. 그들은 모두 에 있습니다.eg2.o, 아직 보이지 않는 것입니다.그래서 링커는 아무 소용이 없다고 판단합니다.libz. 도달하면eg2.o, 프로그램에 추가한 다음 에 대한 정의되지 않은 참조를 가집니다.zlibVersion, 연결 순서가 완료되고, 해당 참조가 해결되지 않아 연결이 실패합니다.

마지막으로.pkg-config예제 2의 변형은 이제 명백한 설명을 갖습니다.셸 후:

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 툴체인을 구성하지 않은 디스트론 중 하나이기 때문입니다.

과거에는 유닉스 계열 시스템이 정적 라이브러리와 공유 라이브러리를 서로 다른 규칙으로 연결하는 것이 일반적이었습니다.연결 순서의 정적 라이브러리는 예제 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컴파일 + 연결을 원하는 것으로 파악합니다.그래서 두 명령을 실행한 것처럼 뒤에서 컴파일 명령을 생성하고 실행한 다음 연결 명령을 생성하고 실행합니다.

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

따라서 두 명령을 실행할 와 마찬가지로 연결이 실패합니다.당신이 그 실패에서 알아차린 유일한 차이점은 gcc가 컴파일 + 링크 케이스에서 임시 객체 파일을 생성했다는 것입니다. 왜냐하면 당신이 그것을 사용하라고 말하는 것이 아니기 때문입니다.eg1.o 수 있습니다 확인할 수 있습니다.

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

대신:

eg1.o: In function `main':

참고 항목

상호 종속적인 링크된 라이브러리가 지정된 순서가 잘못되었습니다.

상호 의존적인 라이브러리를 순서를 잘못 지정하는 것은 정의를 제공하는 파일보다 링크에서 나중에 제공되는 것에 대한 정의를 필요로 하는 파일을 얻을 수 있는 한 가지 방법일 뿐입니다.라이브러리를 참조하는 개체 파일 앞에 두는 것도 같은 실수를 저지르는 또 다른 방법입니다.

링커 스크립트를 지원하지 않는 GNUld 주변의 래퍼

일부 .so 파일은 실제로 GNU lld linker 스크립트입니다. 예를 들어 libtbb.따라서 파일은 다음 내용을 포함하는 ASCII 텍스트 파일입니다.

INPUT (libtbb.so.2)

일부 더 복잡한 빌드에서는 이를 지원하지 않을 수 있습니다.예를 들어 컴파일러 옵션에 -v를 포함하면 메인cc 래퍼 mwdip이 링크할 라이브러리의 자세한 출력 목록에서 링커 스크립트 명령 파일을 폐기하는 것을 볼 수 있습니다.간단히 해결할 수 있는 방법은 링커 스크립트 입력 명령 파일을 파일의 복사본(또는 symlink)으로 대체하는 것입니다.

cp libtbb.so.2 libtbb.so

또는 -l 인수를 대신하여 .so의 전체 경로로 대체할 수 있습니다.-ltbb하다/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

템플릿 친구가 되는 중...

친구 연산자(또는 함수)와 함께 템플릿 유형의 코드 스니펫이 주어지면;

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

operator<<는 template 이외의 함수로 선언되고 있습니다.에 rT와 함께 사용되는Foo, templ를 하지 않는 것이 필요합니다.operator<<. 예를 들어, 유형이 있는 경우Foo<int>다음과 같이 운영자 구현이 있어야 합니다.

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<< <int>인스턴스화는 인스턴스화의 개인 멤버에 액세스하기 위해 제한됩니다.Foo<int>.

대안은 다음과 같습니다.

  • 다음과 같이 우정이 템플릿의 모든 인스턴스로 확장되도록 허용합니다.

    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에서 템플릿 친구에 대한 추가 읽기와 C++ FAQ가 있습니다.

위의 기법을 보여주는 코드 목록입니다.


오류 코드 샘플에 대한 참고 사항으로, g++는 다음과 같이 경고합니다.

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)

포함 경로가 다른 경우

헤더 파일과 관련 공유 라이브러리(.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자신의 프로그램에서 무슨 일이 있었는지(pun의 의도) 궁금해하며 머리말을 긁게 됩니다.

이것이 실제 세계에서 어떻게 일어날 수 있는지에 대한 예를 아래에 설명합니다.

예제를 사용한 추가 세부 사항

저는 두가지 프로젝트가 있습니다.graphics.lib그리고.main.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컴파일러에 관한 한 두 프로젝트에서 다른 유형입니다.이는 두 개의 약간 다른 포함 파일(아마도 포함 파일은 두 개의 다른 버전의 라이브러리)에서 가져온 것이기 때문에 발생할 수 있습니다.

링커 디버깅

Visual Studio를 사용하는 경우 덤프빈은 친구입니다.다른 컴파일러들도 비슷한 도구를 가지고 있을 겁니다.

프로세스는 다음과 같습니다.

  1. 링커 오류에 주어진 이상한 망깨진 이름을 주목하세요. (예: draw@graphics@X)YZ).
  2. 라이브러리에서 내보낸 기호를 텍스트 파일로 덤프합니다.
  3. 내보낸 관심 기호를 검색하면, 망치는 이름이 다릅니다.
  4. 왜 망개진 이름들이 다르게 되었는지 주목해보세요.소스 코드에서 매개 변수 유형이 동일하게 보이지만 서로 다른 것을 확인할 수 있습니다.
  5. 그들이 다른 이유.위의 예에서는 포함 파일이 다르기 때문에 서로 다릅니다.

[1] 프로젝트란 라이브러리 또는 실행 파일을 생성하기 위해 함께 연결된 소스 파일 집합을 의미합니다.

편집 1: 이해하기 쉽도록 첫 번째 섹션을 다시 작성했습니다.다른 부분은 수정해야 할 사항이 있으면 아래에 댓글을 달아주시기 바랍니다.감사합니다!

일치하지 않음UNICODE정의들

Windows UNICO 빌드는 다음과 같이 구성됩니다.TCHAR등으로 정의되는wchar_t등. 함께 건축하지 않을 때UNICODE빌드로 정의됨TCHAR로 정의된char그 외에 이것들은UNICODE그리고._UNICODE정의는 모든 ""T 문자열 유형에 영향을 미칩니다.LPTSTR,LPCTSTR그리고 그들의 엘크.

다음과 함께 하나의 라이브러리 구축UNICODE정의를 내리고 그것을 다음과 같은 프로젝트에 연결하려고 시도합니다.UNICODE다 합니다. 정의가 일치하지 않을 경우 링커 오류가 발생합니다.TCHAR;charwchar_t.

오류는 일반적으로 a 값이 a인 함수를 포함합니다.char아니면wchar_t파생된 유형, 다음을 포함할 수 있습니다.std::basic_string<>그 외에도.코드에서 영향을 받는 기능을 탐색할 때, 종종 다음과 같은 참조가 있을 것입니다.TCHAR아니면std::basic_string<TCHAR>코드가 원래 유니코드와 Multi-Byte Character(또는 "좁은") 빌드 모두를 위해 의도되었음을 알려주는 표시입니다.

이를 수정하기 위해서는 모든 필요한 라이브러리와 프로젝트를 일관된 정의로 구축합니다.UNICODE(그리고_UNICODE).

  1. 이 작업은 다음 중 하나를 사용하여 수행할 수 있습니다.

    #define UNICODE
    #define _UNICODE
    
  2. 또는 프로젝트 설정에서.

    Project Properties > 일반 > Project Defaults > Character Set

  3. 또는 명령 줄에.

    /DUNICODE /D_UNICODE
    

UNICO를 사용하지 않을 경우 의 정의가 설정되지 않았는지, 프로젝트에서 다중 문자 설정이 사용되고 일관성 있게 적용되는지 확인할 수 있습니다.

"Release" 빌드와 "Debug" 빌드 간에도 일관성을 유지하는 것을 잊지 마십시오.

청소 및 재구축

빌드를 "클린"하면 이전 빌드, 실패한 빌드, 불완전한 빌드 및 기타 빌드 시스템과 관련된 빌드 문제에서 주변에 방치되어 있을 수 있는 "죽은 목재"를 제거할 수 있습니다.

일반적으로 IDE나 빌드에는 어떤 형태의 "깨끗한" 기능이 포함되지만, 이 기능은 올바르게 구성되지 않거나(예: 수동 makefile에서) 실패할 수 있습니다(예: 중간 또는 결과 바이너리는 읽기 전용입니다).

"clean"이 완료되면 "clean"이 성공하고 생성된 모든 중간 파일(예: 자동 makefile)이 성공적으로 제거되었는지 확인합니다.

프로세스는 최종 수단으로 간주될 수 있지만 종종 좋은 단계입니다. 특히 오류와 관련된 코드가 최근에 추가된 경우(로컬 또는 소스 저장소에서).

""에가 .const언의(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;

그 대신에 한 사람은 다음과 같이 선언할 수 있습니다.const명시적인 경우 file1.cpp의 변수extern

답변이 여러 개인 꽤 오래된 질문이지만 불분명한 "미정 참조" 오류를 해결하는 방법을 공유하고자 합니다.

여러 버전의 라이브러리

저는 가명을 사용해서 다음과 같이 언급하고 있었습니다.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의 <실험::filesystem>에 위의 코드가 포함되어 있습니다.
  • file.cpp, file.h의 구현, #include의 "file.h"
  • main.cpp #include의 <filesystem>과 "file.h"

main.cpp와 file.h에서 사용되는 다른 라이브러리를 참고합니다.main.cpp #에 "파일"이 포함되어 있기 때문입니다.h" <filesystem> 이후 사용된 파일시스템 버전은 C++17입니다.저는 다음과 같은 명령어로 프로그램을 컴파일하곤 했습니다.

$g++ -g -std=c++17 -c main.cpp->> main.cpp를 main으로 합니다.o로 컴파일합니다.
$g++ -g -std=c++17 -c file.cpp->는 및> file.cpp와 파일로 합니다.h 를 파일입니다.o로 컴파일합니다.
$g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> -> main을 합니다.o 와 파일o를 연결합니다.

이렇게 해서 main.ofile.o를 참조했지만 file.o를 참조했기 때문에 main.o에 포함되어 있고 main.o에 사용되는 모든 함수는 "미정의 참조" 오류를 주었습니다.

결의안

이 문제를 해결하기 위해 file.h의 <experimental::filesystem><filesystem>으로 변경해야 했습니다.

공유 라이브러리에 대한 링크 시 사용된 기호가 숨겨져 있지 않은지 확인합니다.

gcc의 기본 동작은 모든 심볼이 보이는 것입니다.그러나 번역 유닛이 옵션으로 구축된 경우-fvisibility=hidden, 로 표시된 유일한 기능/symbols__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++를 사용한 경우).

윈도우-dll과 유사하게, 예를 들어, 어떤 사람은 정의로 공공 기능을 표시할 것입니다.DLL_PUBLIC정의:

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

DLL_PUBLIC int my_public_function(){
  ...
}

이것은 윈도우/MSVC 버전과 대략적으로 일치합니다.

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

가시성에 대한 자세한 내용은 gcc wiki에서 확인할 수 있습니다.


번역 단위가 다음과 함께 컴파일되는 경우-fvisibility=hidden결과적인 기호는 여전히 외부 연결을 갖습니다(shown는 다음과 같이 대문자 기호 유형과 함).nm 및 가 될 외부 할 수 및 객체 파일이 정적 라이브러리의 일부가 될 경우 외부 링크에 문제없이 사용할 수 있습니다.개체 파일이 공유 라이브러리에 연결된 경우에만 연결이 로컬이 됩니다.

개체 파일에서 숨겨진 기호를 찾으려면 다음을 실행합니다.

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

"Undefined Reference" 오류는 프로그램에 객체 이름(클래스, 함수, 변수 등)에 대한 참조가 있고 링크러가 연결된 모든 객체 파일 및 라이브러리에서 해당 참조를 검색하려고 할 때 해당 참조의 정의를 찾을 수 없을 때 발생합니다.

따라서 링커가 링크된 개체의 정의를 찾을 수 없으면 "정의되지 않은 참조" 오류를 발생시킵니다.정의상 이 오류는 연결 프로세스의 후기 단계에서 발생합니다."정의되지 않은 참조" 오류를 발생시키는 다양한 이유가 있습니다.

몇 가지 가능한 이유(더 자주 발생):

#1) 개체에 대한 정의가 제공되지 않음

이것이 "정의되지 않은 참조" 오류를 발생시키는 가장 간단한 이유입니다.프로그래머가 개체를 정의하는 것을 잊었을 뿐입니다.

다음의 C++ 프로그램을 생각해 봅니다.여기서는 기능의 프로토타입만 명시한 후 메인 기능에 사용하였습니다.

#include <iostream>
int func1();
int main()
{
     
    func1();
}

출력:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

그래서 이 프로그램을 컴파일하면 "'func1()'에 대한 미정의 참조"라는 링커 오류가 발생합니다.

이러한 오류를 제거하기 위해 함수 func1의 정의를 제시하여 다음과 같이 프로그램을 수정합니다.이제 프로그램은 적절한 출력을 제공합니다.

#include <iostream>
using namespace std;
int func1();
 
int main()
{
     
    func1();
}
int func1(){
    cout<<"hello, world!!";
}

출력:

hello, world!!

#2) 사용된 개체의 잘못된 정의(서명이 일치하지 않음)

그러나 "정의되지 않은 참조" 오류의 또 다른 원인은 잘못된 정의를 지정하는 경우입니다.우리는 프로그램에서 어떤 대상이든 사용하며, 그 정의는 다릅니다.

다음의 C++ 프로그램을 생각해 봅니다.여기 func1()로 전화를 걸었습니다.프로토타입은 int func1()입니다.하지만 그것의 정의는 그것의 원형과 일치하지 않습니다.우리가 보는 바와 같이 함수의 정의에는 함수에 대한 파라미터가 포함되어 있습니다.

따라서 프로그램이 컴파일될 때 프로토타입과 함수 호출 일치로 인해 컴파일이 성공합니다.그러나 링커가 함수 호출을 자신의 정의와 연결하려고 할 때 문제를 발견하고 오류를 "정의되지 않은 참조"로 발행합니다.

#include <iostream>
using namespace std;
int func1();
int main()
{
     
    func1();
}
int func1(int n){
    cout<<"hello, world!!";
}

출력:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

따라서 이러한 오류를 방지하기 위해 프로그램에서 모든 개체의 정의와 사용법이 일치하는지 확인하기만 하면 됩니다.

#3) 개체 파일이 제대로 연결되지 않음

이 문제로 인해 "정의되지 않은 참조" 오류가 발생할 수도 있습니다.여기서는 둘 이상의 소스 파일을 가질 수 있으며 독립적으로 컴파일할 수도 있습니다.이렇게 하면 개체가 제대로 연결되지 않아 "정의되지 않은 참조" 상태가 됩니다.

다음의 두 C++ 프로그램을 생각해 보십시오.첫 번째 파일에서는 두 번째 파일에 정의된 "print()" 함수를 사용합니다.이 파일들을 따로 컴파일하면 첫 번째 파일은 인쇄 기능에 대해 "미정의 참조"를 제공하고 두 번째 파일은 메인 기능에 대해 "미정의 참조"를 제공합니다.

int print();
int main()
{
    print();
}

출력:

main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status

int print() {
    return 42;
}

출력:

(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status

이 오류를 해결하는 방법은 두 파일을 동시에 컴파일하는 것입니다(예: g++를 사용).

이미 논의된 원인 외에도 다음과 같은 이유로 인해 "미정 참조"가 발생할 수도 있습니다.

#4) 잘못된 프로젝트 유형

비주얼 스튜디오와 같은 C++ IDE에서 잘못된 프로젝트 유형을 지정하고 프로젝트가 예상하지 못한 작업을 수행하려고 하면 "정의되지 않은 참조"가 발생합니다.

#5) 라이브러리 없음

프로그래머가 라이브러리 경로를 제대로 지정하지 않았거나 지정하는 것을 완전히 잊어버린 경우, 프로그램이 라이브러리에서 사용하는 모든 참조에 대한 "정의되지 않은 참조"를 얻게 됩니다.

#6) 종속 파일이 컴파일되지 않음

프로그래머는 프로젝트를 컴파일할 때 컴파일러가 모든 종속성을 찾아 성공적으로 컴파일할 수 있도록 프로젝트의 모든 종속성을 사전에 컴파일해야 합니다.의존성이 누락된 경우 컴파일러는 "정의되지 않은 참조"를 제공합니다.

위에서 설명한 원인을 제외하고, "미정 참조" 오류는 다른 많은 상황에서 발생할 수 있습니다.하지만 결론은 프로그래머가 잘못된 점을 발견했다는 것이고 이 오류를 방지하기 위해서는 수정되어야 합니다.

언급URL : https://stackoverflow.com/questions/59193898/mariadb-connect-linker-err%c6%a1r-with-c

반응형