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

정의되지 않은 참조 / 해결되지 않은 외부 심볼 오류는 무엇입니까? 일반적인 원인은 무엇이며 어떻게 해결 / 방지합니까?

자유롭게 편집하거나 추가하십시오.



답변

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

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

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

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

지정된 오류는이 마지막 컴파일 단계에서 발생하며 가장 일반적으로 연결이라고합니다. 기본적으로 많은 구현 파일을 객체 파일 또는 라이브러리로 컴파일했으며 이제 함께 작동하도록 만들고 싶습니다.

당신이 기호를 정의 말 aa.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

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


답변

반원 :

순수한 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은 “라이브러리”라는 섹션의 각 함수 맨 아래에있는 상자에 정보를 넣습니다.


답변

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

전형적인 변수 선언은

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

좋아, 우리는 그것을 해킹 🙂

따라서 링커가 객체 파일에서 전역 심볼을 찾을 수없는 경우 “정의되지 않은 참조 / 해결되지 않은 외부 심볼 오류”가 발생합니다.


답변

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