etc./StackOverFlow

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

청렴결백한 만능 재주꾼 2022. 1. 8. 01:38
반응형

질문자 :Luchian Grigore


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



C++ 프로그램 컴파일은 2.2 (참조용 Keith Thompson에 대한 크레딧)에 지정된 대로 여러 단계로 수행됩니다.

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

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

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

지정된 오류는 가장 일반적으로 링크라고 하는 컴파일의 마지막 단계에서 발생합니다. 기본적으로 많은 구현 파일을 개체 파일이나 라이브러리로 컴파일하고 이제 함께 작동하도록 하려는 것을 의미합니다.

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

Microsoft Visual Studio를 사용하는 경우 프로젝트에서 .lib 파일을 생성하는 것을 볼 수 있습니다. 여기에는 내보낸 기호 테이블과 가져온 기호 테이블이 포함됩니다. 가져온 심볼은 링크 대상 라이브러리에 대해 확인되고 내보낸 심볼은 해당 .lib (있는 경우)를 사용하는 라이브러리에 제공됩니다.

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

일반적인 오류 메시지는 error LNK2001 , error LNK1120 , Microsoft Visual Studio의 error LNK2019GCC의 경우 symbolName에 대한 undefined reference to 입니다.

코드:

 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

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


Luchian Grigore

클래스 멤버:

순수한 virtual 소멸자는 구현이 필요합니다.

소멸자를 순수로 선언하려면 일반 함수와 달리 정의해야 합니다.

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

이것은 객체가 암시적으로 소멸될 때 기본 클래스 소멸자가 호출되므로 정의가 필요하기 때문에 발생합니다.

virtual 메서드는 구현되거나 순수하게 정의되어야 합니다.

이것은 순수 선언이 더미 vtable을 생성하고 함수를 사용하지 않고 링커 오류가 발생할 수 있다는 추가 추론과 함께 정의가 없는 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-use에는 위에서 설명한 대로 여전히 네임스페이스 범위 정의가 필요합니다. static const 데이터 멤버에 대해 클래스 내부에서 초기화를 허용합니다.


Luchian Grigore

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

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

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

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

여기서 libraryName 은 플랫폼별 추가 사항 없이 라이브러리의 베어 이름입니다. 따라서 예를 들어 Linux에서 라이브러리 파일은 일반적으로 libfoo.so -lfoo 만 작성합니다. Windows에서는 동일한 파일을 foo.lib 라고 할 수 있지만 동일한 인수를 사용합니다. -L‹directory› 사용하여 해당 파일을 찾을 수 있는 디렉토리를 추가해야 할 수도 있습니다. -l 또는 -L 뒤에 공백을 쓰지 않도록 합니다.

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

MSVS 에서 프로젝트에 추가된 파일은 자동으로 개체 파일이 함께 링크되고 lib 파일이 생성됩니다(일반적인 사용). 별도의 프로젝트에서 기호를 사용하려면 프로젝트 설정에 lib 이것은 프로젝트 속성의 링커 섹션에서 Input -> Additional Dependencies 됩니다. lib 파일의 경로 Linker -> General -> Additional Library Directories 추가해야 합니다. lib 파일과 함께 제공되는 타사 라이브러리를 사용할 때 실패하면 일반적으로 오류가 발생합니다.

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

Windows 프로그래밍에서 필요한 라이브러리를 연결하지 않았다는 신호는 확인되지 않은 기호의 이름이 __imp_ 시작한다는 것입니다. 문서에서 함수 이름을 찾아보면 어떤 라이브러리를 사용해야 하는지 나와 있어야 합니다. 예를 들어, MSDN은 "라이브러리"라는 섹션의 각 기능 하단에 있는 상자에 정보를 넣습니다.


Luchian Grigore

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

일반적인 변수 선언은 다음과 같습니다.

 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)

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

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

컴파일러의 오류 메시지는 종종 선언되었지만 정의되지 않은 변수 또는 함수의 전체 선언을 제공합니다. 귀하가 제공한 정의와 밀접하게 비교하십시오. 모든 세부 사항이 일치하는지 확인하십시오.


Luchian Grigore

상호 의존적으로 연결된 라이브러리가 지정된 순서가 잘못되었습니다.

라이브러리가 서로 의존하는 경우 라이브러리가 링크되는 순서는 중요하지 않습니다. 라이브러리의 경우 일반적으로, A 라이브러리에 따라 B , 다음 libA 앞에 나타나야합니다 libB 링커 플래그에서.

예를 들어:

 // Bh #ifndef B_H #define B_H struct B { B(int); int x; }; #endif // B.cpp #include "Bh" B::B(int xx) : x(xx) {} // Ah #include "Bh" struct A { A(int x); B b; }; // A.cpp #include "Ah" A::A(int x) : b(x) {} // main.cpp #include "Ah" int main() { A a(5); return 0; };

라이브러리 생성:

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

엮다:

 $ g++ main.cpp -L. -lB -lA ./libA.a(Ao): 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

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


Svalorzen

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

"정의되지 않은 참조/확인되지 않은 외부 기호"가 무엇인지 설명하려고 합니다.

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

좋아, 우리는 그것을 해킹 :)

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


Community Wiki

기호는 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" }

Luchian Grigore

다른 모든 방법이 실패하면 다시 컴파일하십시오.

최근에 문제가 되는 파일을 다시 컴파일하여 Visual Studio 2012에서 해결되지 않은 외부 오류를 제거할 수 있었습니다. 다시 빌드하면 오류가 사라졌습니다.

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


sgryzko

템플릿 구현이 표시되지 않습니다.

전문화되지 않은 템플릿은 해당 템플릿을 사용하는 모든 번역 단위에서 해당 정의를 볼 수 있어야 합니다. 즉, 템플릿 정의를 구현 파일로 분리할 수 없습니다. 구현을 분리해야 하는 경우 일반적인 해결 방법은 템플릿을 선언하는 헤더 끝에 포함 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 정의를 헤더 파일로 이동하거나 이를 사용하는 번역 단위가 볼 수 있는 위치로 이동해야 합니다.

특수화된 템플릿은 구현 파일에서 구현될 수 있으며 구현이 표시될 필요는 없지만 전문화는 이전에 선언되어야 합니다.

추가 설명과 다른 가능한 솔루션(명시적 인스턴스화)은 이 질문과 답변을 참조하십시오.


Luchian Grigore

이것은 모든 VC++ 프로그래머가 몇 번이고 다시 본 가장 혼란스러운 오류 메시지 중 하나입니다. 먼저 명확하게 합시다.

A. 상징이란 무엇입니까? 한마디로 상징은 이름이다. 변수 이름, 함수 이름, 클래스 이름, typedef 이름 또는 C++ 언어에 속하는 이름과 기호를 제외한 모든 것이 될 수 있습니다. 종속성 라이브러리(다른 사용자 정의)에 의해 사용자 정의되거나 도입되었습니다.

B. 외부란 무엇입니까? VC++에서 모든 소스 파일(.cpp, .c 등)은 번역 단위로 간주되고 컴파일러는 한 번에 하나의 단위를 컴파일하고 현재 번역 단위에 대해 하나의 개체 파일(.obj)을 생성합니다. (이 소스 파일에 포함된 모든 헤더 파일은 사전 처리되어 이 번역 단위의 일부로 간주됩니다.) 번역 단위 내의 모든 것은 내부로 간주되고 나머지는 외부로 간주됩니다. extern , __declspec (dllimport) 등과 같은 키워드를 사용하여 외부 기호를 참조할 수 있습니다.

C. "해결"이란 무엇입니까? 해결은 연결 시간 용어입니다. 연결 시 링커는 내부적으로 정의를 찾을 수 없는 개체 파일의 모든 기호에 대한 외부 정의를 찾으려고 시도합니다. 다음을 포함하는 이 검색 프로세스의 범위:

  • 컴파일시 생성된 모든 오브젝트 파일
  • 이 빌딩 애플리케이션의 추가 종속성으로 명시적으로 또는 암시적으로 지정된 모든 라이브러리(.lib).

이 검색 프로세스를 해결이라고 합니다.

D. 마지막으로 왜 Unresolved External Symbol인가? 링커가 내부적으로 정의가 없는 기호에 대한 외부 정의를 찾을 수 없으면 Unresolved External Symbol 오류를 보고합니다.

E. LNK2019의 가능한 원인 : 해결되지 않은 외부 기호 오류. 우리는 이미 이 오류가 링커가 외부 기호의 정의를 찾지 못했기 때문에 발생한다는 것을 알고 있습니다. 가능한 원인은 다음과 같이 정렬할 수 있습니다.

  1. 정의 존재

예를 들어, a.cpp에 foo라는 함수가 정의되어 있는 경우:

 int foo() { return 0; }

b.cpp에서 foo 함수를 호출하기를 원하므로 다음을 추가합니다.

 void foo();

foo() 함수를 선언하고 다른 함수 본문에서 호출하려면 bar() 라고 말합니다.

 void bar() { foo(); }

이제 이 코드를 빌드할 때 foo가 확인되지 않은 기호라고 불평하는 LNK2019 오류가 발생합니다. 이 경우 foo()가 .cpp에 정의되어 있지만 호출하는 것과는 다릅니다(다른 반환 값). 정의가 존재하는 경우입니다.

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

라이브러리의 일부 기능을 호출하고 싶지만 가져오기 라이브러리가 Project | Properties | Configuration Properties | Linker | Input | Additional Dependency 설정)에 추가되지 않은 경우. 이제 링커는 정의가 현재 검색 범위에 없기 때문에 LNK2019를 보고합니다.


Nima Soroush

모듈/dll에서 메서드/클래스를 잘못 가져오기/내보내기(컴파일러에 따라 다름).

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

Luchian Grigore

WinMain@16 또는 이와 유사한 '비정상적' main() 진입점 참조에 대한 정의되지 않은 참조 (특히 ).

실제 IDE에서 올바른 프로젝트 유형을 선택하지 못했을 수 있습니다. int main(int argc, char** argv); 대신 이러한 진입점 기능(위의 누락된 참조에 지정된 대로)에 Windows 응용 프로그램 프로젝트를 바인딩할 수 있습니다. 서명.

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


다음은 실제 문제에서 더 자세히 처리된 사례 1사례 2 입니다.


πάντα ῥεῖ

또한 타사 라이브러리를 사용하는 경우 올바른 32/64비트 바이너리가 있는지 확인하십시오.


Dula

Microsoft는 링크 타임에 올바른 라이브러리를 참조하기 위해 #pragma

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

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


Niall

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

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

올바른 해결책은 개발자가 업데이트된 패키지를 릴리스한 다음 업그레이드하는 것이지만 VS2012 라이브러리 파일을 가리키는 VS2013에 대한 추가 설정을 해킹하여 저에게 효과적이었습니다.

packagename\build\native\packagename.targets 와 그 파일 내에서 모든 v110 섹션을 복사하여 (솔루션 디렉토리 내의 packages 폴더에서) 패키지를 편집했습니다. 파일 이름 경로를 모두 v110 으로 두는 데 매우 주의하면서 조건 필드 에서 v110v120 으로 변경했습니다. 이것은 단순히 Visual Studio 2013이 2012용 라이브러리에 링크하도록 허용했으며 이 경우에는 작동했습니다.


Malvineous

수천 개의 .cpp 파일과 수천 개의 .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.Cpp.Clean.txt라는 파일에 출력을 씁니다.

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

프로젝트 연결 단계

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

관찰

  1. 링커가 하나의 기호를 찾으면 다른 라이브러리에서 검색하지 않습니다.
  2. 라이브러리를 연결하는 순서 는 중요 합니다.
  3. 링커가 하나의 정적 라이브러리에서 외부 기호를 찾으면 프로젝트의 출력에 기호를 포함합니다. 그러나 라이브러리가 공유(동적)인 경우 출력에 코드( 기호)를 포함하지 않지만 런타임 충돌이 발생할 수 있습니다. 발생하다

이런 종류의 오류를 해결하는 방법

컴파일러 시간 오류:

  • C++ 프로젝트를 구문적으로 올바르게 작성했는지 확인하십시오.

링커 시간 오류

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

user4272649

컴파일러/IDE의 버그

최근에 이 문제가 발생 했는데 Visual Studio Express 2013의 버그로 밝혀졌습니다. 버그를 극복하기 위해 프로젝트에서 소스 파일을 제거하고 다시 추가해야 했습니다.

컴파일러/IDE의 버그일 수 있다고 생각되면 시도할 단계:

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

developerbmw

링커를 사용하여 오류 진단

대부분의 최신 링커에는 다양한 정도로 출력하는 자세한 옵션이 포함되어 있습니다.

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

gcc 및 clang의 경우; 일반적으로 -v -Wl,--verbose 또는 -v -Wl,-v 를 추가합니다. 자세한 내용은 여기에서 확인할 수 있습니다.

MSVC의 경우 /VERBOSE (특히 /VERBOSE:LIB )가 링크 명령줄에 추가됩니다.


Niall

연결된 .lib 파일은 .dll에 연결됩니다.

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

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


kiriloff

링커 오류와 관련하여 사람들이 이 질문을 하는 것 같기 때문에 여기에 추가하겠습니다.

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

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

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


Plankalkül

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

  • 프로그램을 컴파일하고 GCC 도구 체인과 연결하려고 합니다.
  • 귀하의 연결은 필요한 모든 라이브러리 및 라이브러리 검색 경로를 지정합니다.
  • libfoolibbar 의존한다면 연결은 libbar 앞에 libfoo 를 올바르게 넣습니다.
  • 무언가 오류에 undefined reference to 인해 연결이 실패합니다.
  • 그러나 정의되지 않은 모든 #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

pkg-config 와 관련된 예제 2의 변형:

 $ 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에 의해 생성된 연결 명령은 명령줄 순서에서 왼쪽에서 오른쪽으로 연결의 파일을 사용합니다. 파일이 무언가를 참조하고 해당 정의를 포함하지 않는다는 것을 발견하면 은 더 오른쪽에 있는 파일에서 정의를 검색합니다. 결국 정의를 찾으면 참조가 해결됩니다. 참조가 마지막에 확인되지 않은 상태로 남아 있으면 연결이 실패합니다. 링커는 역방향으로 검색하지 않습니다.

먼저, 정적 라이브러리 my_lib.a 예제 1

정적 라이브러리는 객체 파일의 색인화된 아카이브입니다. 링커가 연결 시퀀스에서 -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 대한 참조가 해결되지 않고 연결이 실패합니다.

두 번째, 예제 2 , 공유 라이브러리 libz

공유 라이브러리는 객체 파일이나 이와 유사한 것의 아카이브가 아닙니다. main 기능이 없고 대신 정의한 여러 개의 다른 기호를 노출하여 다른 프로그램이 런타임에 사용할 수 있도록 하는 프로그램 과 훨씬 비슷합니다.

오늘날 많은 Linux 배포판은 언어 드라이버( gcc , g++ , gfortran 등)가 시스템 링커( ld )가 필요에 따라 공유 라이브러리를 연결하도록 지시하도록 GCC 도구 체인을 구성합니다. 그 배포판 중 하나가 있습니다.

이것은 링커가 -lz 를 찾고 이것이 공유 라이브러리(예: /usr/lib/x86_64-linux-gnu/libz.so )를 참조한다는 것을 알아낼 때 libz 내보낸 정의가 추가되었습니다.

그것이 사실이라면 링커는 libz 청크를 복사하지 않고 프로그램에 추가합니다. 대신 프로그램 코드를 다음과 같이 처리합니다.

  • 런타임에 시스템 프로그램 로더는 프로그램을 실행하기 위해 프로그램 사본을 로드할 때마다 libz

  • libz 정의된 항목을 참조할 때마다 해당 참조는 동일한 프로세스에서 libz 복사본이 내보낸 정의를 사용합니다.

libz 내보낸 정의가 있는 단 한 가지, 즉 eg2.c에서 한 번만 참조되는 zlibVersion eg2.c 합니다. 링커가 해당 참조를 프로그램에 추가한 다음 libz 에서 내보낸 정의를 찾으면 참조가 해결됩니다.

그러나 다음과 같이 프로그램을 연결하려고 할 때:

 gcc -o eg2 -lz eg2.o

이벤트의 순서는 예제 1과 같은 방식으로 잘못되었습니다. 링커가 -lz 찾은 시점에서 프로그램에는 아무 eg2.o 에 있으며 아직 본 적이 없습니다. . libz 사용하지 않는다고 결정합니다. eg2.o 에 도달하면 프로그램에 추가한 다음 zlibVersion 에 대한 정의되지 않은 참조가 있으면 연결 시퀀스가 완료됩니다. 해당 참조가 확인되지 않고 연결이 실패합니다.

마지막으로 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 도구 체인을 구성하지 않는 배포판 중 하나이기 때문입니다.

예전에는 유닉스 계열 시스템이 다른 규칙에 따라 정적 라이브러리와 공유 라이브러리를 연결하는 것이 일반적이었습니다. 연결 순서의 정적 라이브러리는 예제 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를 사용하도록 지시하지 않았기 때문에 compile + link의 경우 임시 개체 파일을 생성 eg1.o 입니다. 우리는보다:

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

대신에:

 eg1.o: In function `main':

또한보십시오

상호 의존적으로 연결된 라이브러리가 지정된 순서가 잘못되었습니다.

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


Mike Kinghan

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

일부 .so 파일은 실제로 GNU ld 링커 스크립트입니다 . 예를 들어 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


JDiMatteo

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

친구 연산자(또는 함수)가 있는 템플릿 유형의 코드 조각이 제공됩니다.

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

operator<< 는 템플릿이 아닌 함수로 선언되고 있습니다. Foo 와 함께 사용되는 T 유형에 대해 템플릿이 아닌 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 내의 friend 선언에서 처음 선언된 이름은 X의 가장 안쪽을 둘러싸는 네임스페이스의 멤버가 되지만 네임스페이스 범위에서 일치하는 선언이 다음과 같은 경우가 아니면 조회를 위해 액세스할 수 없습니다(X를 고려하는 인수 종속 조회 제외). 제공...

cppreferenceC++ 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)


Niall

포함 경로가 다른 경우

링커 오류는 헤더 파일과 관련 공유 라이브러리(.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(...); }

팔! 링커 오류가 발생하고 왜 실패하는지 알 수 없습니다. 그 이유는 공통 라이브러리가 동일한 include common_math.h 의 다른 버전을 사용하기 때문입니다. 컴파일러 설정).

이 예제에서 링커는 실제로는 분명히 라이브러리에서 내보내고 있음을 알면서도 draw() 무엇이 잘못되었는지 궁금해하며 머리를 긁적이며 몇 시간을 보낼 수 있습니다. 문제는 매개변수 유형이 약간 다르기 때문에 링커가 다른 서명을 본다는 것입니다. 예제에서 vec3 은 컴파일러에 관한 한 두 프로젝트에서 다른 유형입니다. 이것은 두 개의 약간 다른 포함 파일에서 왔기 때문에 발생할 수 있습니다(포함 파일은 라이브러리의 두 가지 다른 버전에서 가져온 것일 수 있음).

링커 디버깅

DUMPBIN은 Visual Studio를 사용하는 경우 친구입니다. 다른 컴파일러에는 다른 유사한 도구가 있다고 확신합니다.

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

  1. 링커 오류에 제공된 이상한 맹글링된 이름에 유의하십시오. (예: draw@graphics@XYZ).
  2. 라이브러리에서 내보낸 기호를 텍스트 파일로 덤프합니다.
  3. 내보낸 관심 기호를 검색하고 맹글링된 이름이 다르다는 것을 알 수 있습니다.
  4. 왜 엉망이 된 이름이 다른지 주목하십시오. 소스 코드에서 동일하게 보이지만 매개변수 유형이 서로 다른 것을 알 수 있습니다.
  5. 그들이 다른 이유. 위의 예에서는 포함 파일이 다르기 때문에 다릅니다.

[1] 프로젝트란 라이브러리나 실행 파일을 생성하기 위해 함께 링크된 소스 파일 세트를 의미합니다.

편집 1: 이해하기 쉽도록 첫 번째 섹션을 다시 작성했습니다. 수정해야 할 사항이 있으면 아래에 의견을 보내주십시오. 감사 해요!


fafaro

일관되지 않은 UNICODE 정의

Windows UNICODE 빌드는 wchar_t 등으로 정의된 TCHAR 등으로 빌드됩니다. char 등으로 TCHAR 로 빌드로 정의된 UNICODE 로 빌드하지 않을 때 UNICODE_UNICODE 정의는 모든 " T " 문자열 유형에 영향을 미칩니다. LPTSTR , LPCTSTR 및 엘크.

하나의 라이브러리를 구축 UNICODE 정의 프로젝트에 연결을 시도하는 UNICODE 의 정의에 불일치가있을 것이기 때문에 링커 오류가 발생합니다 정의되지 않은 TCHAR ; charwchar_t .

오류에는 일반적으로 char 또는 wchar_t 파생 유형의 값이 포함되며 여기에는 std::basic_string<> 등이 포함될 수 있습니다. 코드에서 영향을 받는 함수를 탐색할 때 TCHAR 또는 std::basic_string<TCHAR> 등에 대한 참조가 종종 있습니다. 이것은 코드가 원래 UNICODE 및 Multi-Byte 둘 다용으로 의도되었음을 알리는 신호입니다. 캐릭터(또는 "좁은") 빌드.

UNICODE (및 _UNICODE )의 일관된 정의를 사용하여 모든 필수 라이브러리 및 프로젝트를 빌드하세요.

  1. 이것은 둘 중 하나를 사용하여 수행할 수 있습니다.

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

    프로젝트 속성 > 일반 > 프로젝트 기본값 > 문자 집합

  3. 또는 명령줄에서;

     /DUNICODE /D_UNICODE

대안도 적용 가능합니다. UNICODE를 사용하지 않으려는 경우 정의가 설정되지 않았는지 및/또는 다중 문자 설정이 프로젝트에서 사용되고 일관되게 적용되었는지 확인하십시오.

"릴리스"와 "디버그" 빌드 간에도 일관성을 유지하는 것을 잊지 마십시오.


Niall

청소 및 재건

빌드를 "깨끗하게"하면 이전 빌드, 실패한 빌드, 불완전한 빌드 및 기타 빌드 시스템 관련 빌드 문제에서 주변에 남아 있을 수 있는 "죽은 나무"를 제거할 수 있습니다.

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

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

프로세스는 최후의 수단으로 볼 수 있지만 좋은 첫 번째 단계인 경우가 많습니다 . 특히 오류와 관련된 코드가 최근에 추가된 경우(로컬 또는 소스 리포지토리에서).


Niall

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 const 변수를 선언할 수 있습니다.


Andreas H.

이것은 여러 답변이 허용되는 꽤 오래된 질문이지만 모호한 "정의되지 않은 참조" 오류를 해결하는 방법을 공유하고 싶습니다.

다양한 버전의 라이브러리

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 의 < Experimental::filesystem > 위의 코드를 포함합니다.
  • file.cpp , file.h의 구현, #include의 " file.h "
  • main.cpp #include 의 < 파일 시스템 > 및 " file.h "

main.cpp 및 file.h에서 사용되는 다른 라이브러리에 유의하십시오. MAIN.CPP 번호는 <파일 시스템> 이후 "file.h"를 include'd 때문에, 파일 시스템의 버전은 C ++ (17) 중 하나가 사용되었다. 다음 명령을 사용하여 프로그램을 컴파일하는 데 사용했습니다.

$ g++ -g -std=c++17 -c main.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 executable main.o file.o -lstdc++fs -> main.o 및 file.o 연결

이 방법은 어떤 기능을 file.o에 포함 된 필요한 것을 main.o를에 사용 path_t main.o를가 언급하기 때문에 "정의되지 않은 참조"오류를 준 std::filesystem::path 하지만 file.o을에 std::experimental::filesystem::path .

해결

이 문제를 해결하려면 file.h의 <experimental::filesystem> 을 <filesystem> 으로 변경하기만 하면 됩니다.


Stypox

공유 라이브러리에 연결할 때 사용된 기호가 숨겨져 있지 않은지 확인하십시오.

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

-C 옵션과 함께 nm 를 사용하여 이름을 demangle할 수도 있습니다(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 위키에서 찾을 수 있습니다.


번역 단위가 -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

ead

inline 지정자를 사용하여 소스 파일에 정의됩니다.

예:-

메인.cpp

 #include "gum.h" #include "foo.h" int main() { gum(); foo f; f.bar(); return 0; }

foo.h (1)

 #pragma once struct foo { void bar() const; };

껌.h (1)

 #pragma once extern void gum();

foo.cpp (1)

 #include "foo.h" #include <iostream> inline /* <- wrong! */ void foo::bar() const { std::cout << __PRETTY_FUNCTION__ << std::endl; }

gum.cpp (1)

 #include "gum.h" #include <iostream> inline /* <- wrong! */ void gum() { std::cout << __PRETTY_FUNCTION__ << std::endl; }

gum (유사하게 foo::bar )이 inline 임을 지정하면 컴파일러는 다음과 같이 gum 을 인라인합니다(선택한 경우).

  • gum 대한 고유한 정의를 내놓지 않으므로
  • gum 의 정의를 참조할 수 있는 기호를 내보내지 않고 대신
  • 모든 통화를 대체하는 gum 의 컴파일 된 신체의 인라인 사본 gum .

당신이 정의하면 그 결과, gum 소스 파일에서 인라인을 gum.cpp , 그것은 오브젝트 파일로 컴파일 gum.o 에 대한 모든 호출하는 gum 인라인 어떠한 기호가있는 링커가 참조 할 수 있습니다 정의되지 않은 gum . gum 대한 참조를 만드는 main.o 와 같은 다른 개체 파일과 함께 gum.o 를 프로그램에 링크하면 링커가 해당 참조를 확인할 수 없습니다. 따라서 연결이 실패합니다.

엮다:

 g++ -c main.cpp foo.cpp gum.cpp

링크:

 $ g++ -o prog main.o foo.o gum.o main.o: In function `main': main.cpp:(.text+0x18): undefined reference to `gum()' main.cpp:(.text+0x24): undefined reference to `foo::bar() const' collect2: error: ld returned 1 exit status

gum 이 호출될 수 있는 모든 소스 파일에서 검의 정의를 볼 수 있는 경우에만 gum inline 즉, 인라인 정의는 gum 이 호출될 수 있는 컴파일하는 모든 소스 파일에 포함 하는 헤더 파일에 있어야 합니다. 다음 두 가지 중 하나를 수행합니다.

정의를 인라인하지 않거나

소스 파일 정의에서 inline 지정자를 제거합니다.

foo.cpp (2)

 #include "foo.h" #include <iostream> void foo::bar() const { std::cout << __PRETTY_FUNCTION__ << std::endl; }

gum.cpp (2)

 #include "gum.h" #include <iostream> void gum() { std::cout << __PRETTY_FUNCTION__ << std::endl; }

다음과 같이 다시 빌드하십시오.

 $ g++ -c main.cpp foo.cpp gum.cpp imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog void gum() void foo::bar() const

성공.

또는 올바르게 인라인

헤더 파일의 인라인 정의:

foo.h (2)

 #pragma once #include <iostream> struct foo { void bar() const { // In-class definition is implicitly inline std::cout << __PRETTY_FUNCTION__ << std::endl; } }; // Alternatively... #if 0 struct foo { void bar() const; }; inline void foo::bar() const { std::cout << __PRETTY_FUNCTION__ << std::endl; } #endif

껌.h (2)

 #pragma once #include <iostream> inline void gum() { std::cout << __PRETTY_FUNCTION__ << std::endl; }

foo.cpp 또는 gum.cpp 가 필요하지 않습니다.

 $ g++ -c main.cpp $ g++ -o prog main.o $ ./prog void gum() void foo::bar() const

Mike Kinghan

출처 : http:www.stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix

반응형