정의되지 않은 참조 / 해결되지 않은 외부 심볼 오류는 무엇입니까? 일반적인 원인은 무엇이며 어떻게 해결 / 방지합니까?
자유롭게 편집하거나 추가하십시오.
정의되지 않은 참조 / 해결되지 않은 외부 심볼 오류는 무엇입니까? 일반적인 원인은 무엇이며 어떻게 해결 / 방지합니까?
자유롭게 편집하거나 추가하십시오.
답변:
2.2 에서 지정한대로 C ++ 프로그램 컴파일은 여러 단계로 수행됩니다 (참조 용 Keith Thompson의 신용) .
번역의 구문 규칙 중 우선 순위는 다음 단계에 따라 지정됩니다 [각주 참조] .
- 실제 소스 파일 문자는 구현 정의 방식으로 필요한 경우 기본 소스 문자 세트 (행 끝 표시기의 개행 문자 소개)에 맵핑됩니다. [한조각]
- 줄 바꿈 문자 바로 뒤에 오는 백 슬래시 문자 (\)의 각 인스턴스는 삭제되어 실제 소스 행을 연결하여 논리 소스 행을 형성합니다. [한조각]
- 소스 파일은 사전 처리 토큰 (2.5)과 일련의 공백 문자 (주석 포함)로 분해됩니다. [한조각]
- 전처리 지시문이 실행되고 매크로 호출이 확장되며 _Pragma 단항 연산자식이 실행됩니다. [한조각]
- 문자 리터럴 또는 문자열 리터럴의 각 소스 문자 세트 멤버와 문자 리터럴 또는 비원시 문자열 리터럴의 각 이스케이프 시퀀스 및 범용 문자 이름은 실행 문자 세트의 해당 멤버로 변환됩니다. [한조각]
- 인접 문자열 리터럴 토큰이 연결되었습니다.
- 토큰을 분리하는 공백 문자는 더 이상 중요하지 않습니다. 각 전처리 토큰은 토큰으로 변환됩니다. (2.7). 결과 토큰은 구문 및 의미 론적으로 분석되고 번역 단위로 번역됩니다. [한조각]
- 번역 된 번역 단위와 인스턴스화 단위는 다음과 같이 결합됩니다. [SNIP]
- 모든 외부 엔티티 참조가 해결되었습니다. 라이브러리 구성 요소는 현재 번역에 정의되지 않은 엔터티에 대한 외부 참조를 충족시키기 위해 연결됩니다. 이러한 모든 변환기 출력은 실행 환경에서 실행하는 데 필요한 정보가 포함 된 프로그램 이미지로 수집됩니다. (강조 광산)
[각주] 실제로는 다른 단계가 함께 접힐 수 있지만 구현은 이러한 개별 단계가 발생하는 것처럼 작동해야합니다.
지정된 오류는이 마지막 컴파일 단계에서 발생하며 가장 일반적으로 연결이라고합니다. 기본적으로 많은 구현 파일을 객체 파일 또는 라이브러리로 컴파일했으며 이제 함께 작동하도록 만들고 싶습니다.
당신이 기호를 정의 말 a
에 a.cpp
. 이제 그 기호를 b.cpp
선언 하고 사용했습니다. 연결하기 전에 단순히 해당 심볼이 어딘가에 정의되었다고 가정 하지만 아직 어디에도 신경 쓰지 않습니다. 연결 단계는 심볼을 찾아서 심볼을 올바르게 링크하는 역할을합니다 b.cpp
(실제로 심볼 을 사용하는 객체 또는 라이브러리에 연결).
Microsoft Visual Studio를 사용하는 경우 프로젝트가 .lib
파일을 생성하는 것을 볼 수 있습니다. 여기에는 내 보낸 심볼 테이블과 가져온 심볼 테이블이 포함됩니다. 가져온 심볼은 링크 한 라이브러리에 대해 분석되고 해당 심볼을 사용하는 라이브러리 .lib
(있는 경우)에 대해 내 보낸 심볼이 제공됩니다 .
다른 컴파일러 / 플랫폼에도 비슷한 메커니즘이 존재합니다.
일반적인 오류 메시지는 error LNK2001
, error LNK1120
, error LNK2019
에 대한 마이크로 소프트 비주얼 스튜디오 와 undefined reference to
symbolname 만 에 GCC .
코드:
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
일반적인 원인은 다음과 같습니다.
#pragma
(Microsoft Visual Studio)를 사용할 때 .lib 확장자가 잘못되었거나 포함되지 않음UNICODE
정의virtual
소멸자는 구현이 필요합니다.소멸자를 순수로 선언하려면 일반 함수와 달리 여전히 정의해야합니다.
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
오브젝트가 내재적으로 소멸 될 때 기본 클래스 소멸자가 호출되므로 정의가 필요합니다.
virtual
메소드는 순수하게 구현되거나 정의되어야합니다.이것은 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;
};
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를 사용하려면 위에서 설명한 네임 스페이스 범위 정의가 여전히 필요합니다. C ++ 11은 모든 static const
데이터 멤버에 대해 클래스 내에서 초기화 할 수 있습니다 .
일반적으로 각 변환 단위는 해당 변환 단위에 정의 된 기호의 정의가 포함 된 객체 파일을 생성합니다. 해당 심볼을 사용하려면 해당 객체 파일과 연결해야합니다.
아래 GCC 명령 줄에서 서로 연결하는 모든 오브젝트 파일을 지정하거나 함께 구현 파일을 컴파일합니다.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
다음 libraryName
은 플랫폼 별 추가없이 라이브러리의 이름입니다. 따라서 예를 들어 Linux 라이브러리 파일은 일반적으로 호출 libfoo.so
되지만 쓰기 만합니다 -lfoo
. Windows에서 동일한 파일은이라고 할 수 foo.lib
있지만 동일한 인수를 사용합니다. 를 사용하여 해당 파일을 찾을 수있는 디렉토리를 추가해야 할 수도 있습니다 -L‹directory›
. -l
또는 뒤에 공백을 쓰지 마십시오 -L
.
들어 엑스 코드 : -> 라이브러리 검색 경로 추가 - 사용자 헤더 검색 경로 추가> 드래그를 프로젝트 폴더에 실제 라이브러리 참조를 놓습니다.
아래 MSVS , 프로젝트에 추가 된 파일은 자동으로 오브젝트 파일을 서로 연결하고이 lib
파일 (일반적인 사용에서) 생성 될 것이다. 별도의 프로젝트에서 기호를 사용하려면 lib
프로젝트 설정에 파일 을 포함해야합니다 . 이는 프로젝트 속성의 링커 섹션에서 수행됩니다 Input -> Additional Dependencies
. ( lib
파일 경로 는에 추가되어야합니다. Linker -> General -> Additional Library Directories
) lib
파일 과 함께 제공되는 타사 라이브러리를 사용할 때 일반적으로 그렇게하지 않으면 오류가 발생합니다.
컴파일에 파일을 추가하는 것을 잊어 버릴 수도 있습니다.이 경우 개체 파일이 생성되지 않습니다. gcc 에서는 파일을 명령 줄에 추가합니다. 에서 MSVS이 프로젝트에 파일을 추가가 (파일 수이기는하지만, 수동으로, 개별적으로 빌드에서 제외) 자동으로 컴파일 할 것이다.
Windows 프로그래밍에서 필요한 라이브러리를 연결하지 않았다는 의미없는 기호는 해석되지 않은 기호의 이름이로 시작한다는 것입니다 __imp_
. 설명서에서 함수 이름을 찾아보고 사용해야하는 라이브러리가 있어야합니다. 예를 들어 MSDN은 "라이브러리"라는 섹션의 각 함수 맨 아래에있는 상자에 정보를 넣습니다.
gcc main.c
대신에 gcc main.c other.c
(일반적으로 프로젝트가 .o 파일을 빌드 할 정도로 커지기 전에 종종하는) 실수를 명시 적으로 다룰 수 있다면 좋을 것 입니다.
전형적인 변수 선언은
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)
불일치의 다른 예는 다음과 같습니다.
컴파일러의 오류 메시지는 종종 선언되었지만 정의되지 않은 변수 또는 함수의 전체 선언을 제공합니다. 제공 한 정의와 밀접하게 비교하십시오. 모든 세부 사항이 일치하는지 확인하십시오.
라이브러리가 서로 의존하는 경우 라이브러리가 연결된 순서는 중요합니다. 도서관이 경우 일반적으로, 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
다시 반복 그래서, 순서 합니까 문제!
"정의되지 않은 참조 / 해결되지 않은 외부 심볼"
"정의되지 않은 참조 / 해결되지 않은 외부 심볼"이 무엇인지 설명하려고합니다.
참고 : 나는 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이 GLOBAL을 바인드했습니다 (기존 LOCAL).
링크
$ g++ src1.o src2.o -o prog
그리고 그것을 실행
$ ./prog
123456789
좋아, 우리는 그것을 해킹 :)
따라서 링커가 객체 파일에서 전역 심볼을 찾을 수없는 경우 "정의되지 않은 참조 / 해결되지 않은 외부 심볼 오류"가 발생합니다.
함수 (또는 변수) 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"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
및 #ifdef __cplusplus [\n] } [\n] #endif
( [\n]
실제 캐리지 리턴되는하지만 난 의견에 이것을 제대로 쓸 수 없습니다).
extern "C" { #include <myCppHeader.h> }
.
다른 모든 방법이 실패하면 다시 컴파일하십시오.
최근에 문제를 일으키는 파일을 다시 컴파일하여 Visual Studio 2012에서 해결되지 않은 외부 오류를 제거 할 수있었습니다. 내가 재건하면 오류가 사라졌습니다.
이는 일반적으로 두 개 이상의 라이브러리에 주기적 종속성이있는 경우 발생합니다. 라이브러리 A는 B.lib의 기호를 사용하려고 시도하고 라이브러리 B는 A.lib의 기호를 사용하려고 시도합니다. 둘 다 처음부터 존재하지 않습니다. A 컴파일을 시도하면 B.lib를 찾을 수 없으므로 링크 단계가 실패합니다. A.lib가 생성되지만 dll은 생성되지 않습니다. 그런 다음 B를 컴파일하면 B.lib가 생성됩니다. 이제 B.lib가 발견되었으므로 A를 다시 컴파일하면 작동합니다.
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
{
};
visibility
및 Windows .def
파일 이 언급되어야합니다.이 파일은 심볼 이름과 존재에 영향을 미치기 때문입니다.
.def
나이에 파일을 사용하지 않았습니다 . 답변을 추가하거나 수정하십시오.
이것은 모든 VC ++ 프로그래머가 반복해서 본 가장 혼란스러운 오류 메시지 중 하나입니다. 먼저 명확성을 만들어 봅시다.
A. 상징이란 무엇입니까? 간단히 말해서 기호는 이름입니다. 변수 이름, 함수 이름, 클래스 이름, typedef 이름 또는 C ++ 언어에 속하는 이름 및 부호를 제외한 모든 이름이 될 수 있습니다. 의존성 라이브러리 (다른 사용자 정의)에 의해 사용자 정의되거나 도입됩니다.
B. 외부는 무엇입니까?
VC ++에서 모든 소스 파일 (.cpp, .c 등)은 변환 단위로 간주되며, 컴파일러는 한 번에 하나의 단위를 컴파일하고 현재 변환 단위에 대해 하나의 오브젝트 파일 (.obj)을 생성합니다. (이 소스 파일에 포함 된 모든 헤더 파일은 사전 처리되어이 번역 단위의 일부로 간주 됨) 번역 단위 내의 모든 항목은 내부로 간주되며 다른 모든 항목은 외부로 간주됩니다. C ++에서, 당신은 같은 키워드를 사용하여 외부 기호를 참조 할 수있다 extern
, __declspec (dllimport)
등등을.
C.“해결”이란 무엇입니까? 해결은 연결 시간 용어입니다. 연결시 링커는 내부적으로 정의를 찾을 수없는 객체 파일의 모든 심볼에 대한 외부 정의를 찾으려고합니다. 이 검색 프로세스의 범위는 다음을 포함합니다.
이 검색 프로세스를 resolve라고합니다.
D. 마지막으로 왜 미해결 외부 기호인가? 링커가 내부적으로 정의가없는 심볼에 대한 외부 정의를 찾을 수없는 경우, 해결되지 않은 외부 심볼 오류를보고합니다.
E. LNK2019의 가능한 원인 : 해결되지 않은 외부 기호 오류. 링커가 외부 심볼의 정의를 찾지 못했기 때문에이 오류가 발생한다는 것을 이미 알고 있습니다. 가능한 원인은 다음과 같이 정렬 할 수 있습니다.
예를 들어, a.cpp에 foo라는 함수가 정의되어 있다면 :
int foo()
{
return 0;
}
b.cpp에서는 foo 함수를 호출하고 싶습니다.
void foo();
함수 foo ()를 선언하고 다른 함수 본문에서 호출하려면 다음과 같이하십시오 bar()
.
void bar()
{
foo();
}
이제이 코드를 빌드하면 LNK2019 오류가 발생하여 foo가 해결되지 않은 기호임을 불평합니다. 이 경우, 우리는 foo ()가 a.cpp에서 정의를 가지고 있지만 우리가 호출하는 것과는 다르다는 것을 알고 있습니다 (다른 반환 값). 정의가 존재하는 경우입니다.
라이브러리에서 일부 함수를 호출하려고하지만 가져 오기 라이브러리가 Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
프로젝트 설정 의 추가 종속성 목록 (:에서 설정 )에 추가되지 않은 경우 . 이제 정의는 현재 검색 범위에 없으므로 링커에서 LNK2019를보고합니다.
특수화되지 않은 템플릿은 해당 정의를 사용하는 모든 번역 단위에 정의가 표시되어야합니다. 즉, 템플릿 정의를 구현 파일로 분리 할 수 없습니다. 구현을 분리해야하는 경우 일반적인 해결 방법은 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
헤더 파일 또는이를 사용하는 번역 단위에 보이는 곳으로 이동해야 합니다.
전문화 된 템플릿은 구현 파일에서 구현할 수 있으며 구현을 볼 필요는 없지만 전문화는 이전에 선언해야합니다.
자세한 설명과 다른 가능한 솔루션 (명시 적 인스턴스화) 은이 질문과 답변을 참조하십시오 .
정의되지 않은 참조 WinMain@16
또는 유사한 '비정상적인' main()
진입 점 참조 (특히비주얼 스튜디오).
실제 IDE에 적합한 프로젝트 유형을 선택하지 못한 경우가 있습니다. IDE는 일반적으로 사용되는 int main(int argc, char** argv);
서명 대신 Windows Application 프로젝트를 이러한 진입 점 함수 (위의 누락 된 참조에 지정된대로)에 바인딩 할 수 있습니다 .
IDE가 일반 콘솔 프로젝트 를 지원 하는 경우 Windows 응용 프로그램 프로젝트 대신이 프로젝트 유형을 선택할 수 있습니다.
새 도구 세트 버전을 사용하려면 Visual Studio NuGet 패키지를 업데이트해야합니다.
방금 libpng와 Visual Studio 2013을 연결하려고 할 때이 문제가 발생했습니다. 문제는 패키지 파일에 Visual Studio 2010 및 2012 용 라이브러리 만 있다는 것입니다.
올바른 해결책은 개발자가 업데이트 된 패키지를 릴리스 한 다음 업그레이드하기를 희망하지만 VS2012 라이브러리 파일을 가리키는 VS2013의 추가 설정을 해킹하여 나에게 도움이되었습니다.
모든 섹션을 복사하여 해당 파일 packages
을 찾고 내부 에서 패키지를 솔루션 디렉토리 내부 의 폴더 에서 편집했습니다 . 나는 변화 에 의 조건 필드에만 모두 같은 파일 이름 경로를 떠날 매우 조심 . 이를 통해 Visual Studio 2013은 2012 년의 라이브러리에 연결될 수 있었으며이 경우에는 효과가있었습니다.packagename\build\native\packagename.targets
v110
v110
v120
v110
수천 개의 .cpp 파일과 수천 개의 .h 파일이있는 c ++로 작성된 큰 프로젝트가 있다고 가정하고이 프로젝트는 10 개의 정적 라이브러리에 의존한다고 가정 해 봅시다. 우리가 Windows에 있고 Visual Studio 20xx에서 프로젝트를 빌드한다고 가정 해 봅시다. Ctrl + F7 Visual Studio를 눌러 전체 솔루션 컴파일을 시작하면 (솔루션에 프로젝트가 하나만 있다고 가정하십시오)
컴파일의 의미는 무엇입니까?
두 번째 컴파일 단계는 링커가 수행합니다. 링커는 모든 객체 파일을 병합하고 최종적으로 출력 (실행 파일 또는 라이브러리 일 수 있음)을 빌드해야합니다.
프로젝트 연결 단계
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
관측
이런 종류의 오류를 해결하는 방법
컴파일러 시간 오류 :
링커 시간 오류
#pragma once
컴파일러가 현재 .cpp에 이미 포함되어있는 경우 하나의 헤더를 포함하지 않도록 컴파일러에 사용최근 에이 문제가 발생 했으며 Visual Studio Express 2013의 버그였습니다 . 버그를 극복하기 위해 프로젝트에서 소스 파일을 제거하고 다시 추가해야했습니다.
컴파일러 / IDE의 버그 일 수 있다고 생각되면 시도하는 단계 :
대부분의 최신 링커에는 다양한 각도로 인쇄하는 자세한 옵션이 있습니다.
gcc와 clang의 경우; 일반적으로 -v -Wl,--verbose
또는 -v -Wl,-v
명령 줄에 추가 합니다. 자세한 내용은 여기를 참조하십시오.
MSVC의 경우, /VERBOSE
특히 /VERBOSE:LIB
링크 명령 줄에 추가됩니다.
/VERBOSE
링커 옵션 .연결된 .lib 파일은 .dll과 연결되어 있습니다.
나는 같은 문제가 있었다. MyProject 및 TestProject 프로젝트가 있다고 가정 해보십시오. MyProject의 lib 파일을 TestProject에 효과적으로 연결했습니다. 그러나이 lib 파일은 MyProject의 DLL이 빌드 될 때 생성되었습니다. 또한 MyProject의 모든 메소드에 대한 소스 코드를 포함하지 않았지만 DLL의 진입 점에만 액세스합니다.
이 문제를 해결하기 위해 MyProject를 LIB로 빌드하고 TestProject를이 .lib 파일에 링크했습니다 (생성 된 .lib 파일을 TestProject 폴더에 복사하여 붙여 넣기). 그런 다음 MyProject를 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로 전환 할 때 갑자기 링커 오류가 발생하면이를 확인해야합니다.
링커 스크립트를 지원하지 않는 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
do 대신)./home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
에 따라 libbar
풋 올바르게 다음 연결, libfoo
전 libbar
.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
그리고 예제 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에서 생성 한 링크 명령은 배포판에서 링크의 파일을 명령 줄 순서대로 왼쪽에서 오른쪽으로 사용합니다. 파일이 무언가를 참조 하고 이에 대한 정의를 포함하지 않는 것을 발견하면 파일 의 오른쪽에있는 정의를 검색합니다. 결국 정의를 찾으면 참조가 해결됩니다. 끝에 참조가 해결되지 않은 상태로 남아 있으면 연결이 실패합니다. 링커는 뒤로 검색하지 않습니다.
먼저 정적 라이브러리가있는 예제 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
끝나는 미해결 및 링크 실패.
두 번째, 예 2 , 공유 라이브러리libz
공유 라이브러리는 객체 파일 또는 이와 유사한 것의 아카이브가 아닙니다. 함수 가없는 프로그램 과 비슷하지만 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입니다.
연계 :
gcc -o eg2 -lz eg2.o
당신을 위해 잘 작동합니다!
(또는 : 페도라 23에서는 링크가 잘 작동했지만 우분투 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':
상호 의존적 인 링크 된 라이브러리가 지정된 순서가 잘못되었습니다
상호 의존적 인 라이브러리를 잘못된 순서 로 놓는 것은 정의를 제공 하는 파일보다 나중에 링크에 나오는 것들에 대한 정의 가 필요한 파일을 얻을 수있는 한 가지 방법 일뿐 입니다. 라이브러리를 참조하는 오브젝트 파일 앞에 라이브러리를 두는 것도 같은 실수를하는 또 다른 방법입니다.
친구 연산자 (또는 함수)가있는 템플리트 유형의 코드 스 니펫이 제공됩니다.
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
는 operator<<
비 템플릿 함수로 선언되고있다. T
와 함께 사용되는 모든 유형에 Foo
대해 템플릿이 없어야 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
되면 헤더가 긁 혀서 무슨 일이 일어 났는지 궁금해 할 것입니다.
현실에서 어떻게 이런 일이 일어날 수 있는지에 대한 예가 아래에 설명되어 있습니다.
두 가지 프로젝트가 있습니다 : 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
컴파일러에 관한 한 두 프로젝트에서 다른 유형입니다. 이것은 파일이 약간 다른 두 개의 포함 파일에서 나왔기 때문에 발생할 수 있습니다 (포함 파일은 서로 다른 두 버전의 라이브러리에서 온 것 같습니다).
DUMPBIN은 Visual Studio를 사용하는 경우 친구입니다. 다른 컴파일러에는 다른 유사한 도구가 있다고 확신합니다.
과정은 다음과 같습니다.
[1] 프로젝트 라 함은 라이브러리 또는 실행 파일을 생성하기 위해 서로 링크 된 소스 파일 세트를 의미합니다.
편집 1 : 이해하기 쉽도록 첫 번째 섹션을 다시 작성했습니다. 다른 사항을 수정해야하는지 알려면 아래에 의견을 남겨주세요. 감사!
UNICODE
정의윈도우 유니 코드 빌드로 구축 TCHAR
등으로 정의되고 wchar_t
있는 건물하지 않을 경우 등 UNICODE
으로 빌드 정의 TCHAR
정의 char
이 등 UNICODE
과 _UNICODE
정의는 모든 영향 " T
"문자열 유형 ; LPTSTR
, LPCTSTR
그들의 엘크.
UNICODE
정의 된 라이브러리 하나를 빌드하고 정의 UNICODE
되지 않은 프로젝트에서 라이브러리 를 연결하려고하면 의 정의에 불일치가 있으므로 링커 오류가 발생합니다 TCHAR
. char
대 wchar_t
.
에러는 일반적으로 값 함수 포함 char
또는 wchar_t
파생 형은 다음을 포함 할 수 std::basic_string<>
도 등. 코드의 영향을받는 기능을 통해 검색 할 때 종종이 참조 될 것이다 TCHAR
또는 std::basic_string<TCHAR>
등이 코드가 원래 ( "축소"또는) 빌드를 유니 코드와 멀티 바이트 문자 모두를위한되었다는 고자질하는 표지판입니다 .
이 문제를 해결하려면 UNICODE
(and _UNICODE
) 의 일관된 정의로 필요한 모든 라이브러리와 프로젝트를 빌드하십시오 .
이것은 어느 쪽이든 할 수 있습니다;
#define UNICODE
#define _UNICODE
또는 프로젝트 설정에서;
프로젝트 속성> 일반> 프로젝트 기본값> 문자 세트
또는 명령 행에서;
/DUNICODE /D_UNICODE
대안은 UNICODE를 사용하지 않을 경우, 정의가 설정되지 않았는지 및 / 또는 다중 문자 설정이 프로젝트에서 사용되고 일관되게 적용되는지 확인하십시오.
"릴리스"와 "디버그"빌드간에 일관성을 유지하는 것을 잊지 마십시오.
빌드의 "깨끗한"은 이전 빌드, 실패한 빌드, 불완전한 빌드 및 기타 빌드 시스템 관련 빌드 문제에서 남아있을 수있는 "죽은 나무"를 제거 할 수 있습니다.
일반적으로 IDE 또는 빌드에는 어떤 형태의 "clean"기능이 포함되지만, 이는 올바르게 구성되지 않았거나 (예 : 수동 makefile에서) 실패 할 수 있습니다 (예 : 중간 또는 결과 바이너리가 읽기 전용).
"clean"이 완료되면 "clean"이 성공하고 생성 된 모든 중간 파일 (예 : 자동화 된 makefile)이 성공적으로 제거되었는지 확인하십시오.
이 과정은 최종 수단으로 볼 수 있지만 종종 좋은 첫 단계입니다 . 특히 오류와 관련된 코드가 최근에 추가 된 경우 (로컬 또는 소스 리포지토리에서).
const
변수 선언 / 정의 에서 "extern"이 누락 됨 (C ++ 만 해당)C에서 온 사람들 const
에게는 C ++에서 전역 변수에 내부 (또는 정적) 연결 이 있다는 것이 놀랍습니다 . 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의 세 파일이 있다고 가정 해 봅시다.
main.cpp 및 file.h에서 사용되는 다른 라이브러리에 유의하십시오. MAIN.CPP 번호가 "include'd 때문에 file.h를 <후" 파일 시스템 > 파일 시스템의 버전이 사용 하였다 는 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> 으로 변경 해야했습니다 .
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
숨겨진 / 로컬 기호는 코드 섹션의 경우`T 대신에 nm
소문자 기호 유형으로 표시됩니다 t
.
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
nm
옵션 -C
을 사용 하여 이름을 엉 키게 할 수도 있습니다 (C ++가 사용 된 경우).
Windows-dll과 유사하게 다음과 같이 정의 된 public 함수를 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
다른 아키텍처
다음과 같은 메시지가 표시 될 수 있습니다.
library machine type 'x64' conflicts with target machine type 'X86'
이 경우 사용 가능한 기호가 컴파일하는 것과 다른 아키텍처에 대한 것임을 의미합니다.
Visual Studio에서 이는 잘못된 "플랫폼"으로 인한 것이며 올바른 버전을 선택하거나 올바른 버전의 라이브러리를 설치해야합니다.
Linux에서는 잘못된 라이브러리 폴더 ( 예 : lib
대신 사용)로 인한 것일 수 있습니다 lib64
.
MacOS에는 두 아키텍처를 동일한 파일로 제공하는 옵션이 있습니다. 링크는 두 버전이 모두있을 것으로 예상하지만 하나만 있습니다. 라이브러리가 선택되는 잘못된 lib
/ lib64
폴더에 문제가있을 수도 있습니다 .