Programming Language/C,C++

C,C++] C++코드와 C 코드의 링크, Mangling 규칙

TwinParadox 2017. 11. 25. 11:56
728x90

C언어는 다른 언어들에 비해(C++, Java 등) 역사가 길어 그동안 작성된 라이브러리, 소스가 많이 존재하며 이것들이 각기 다른 분야에서 활발히 사용되고 있다. 만일 C++ 프로그램을 개발할 때 기존에 작성된 C의 라이브러리, 소스를 사용하게 될 경우가 있는데 이때 C++ 프로그램에서 C 코드를 연결해 사용하는 방법을 알고 있다면 비교적 쉽게 다룰 수 있다. 서로 다른 언어를 연결하는 것은 쉬운 일은 아니지만, C++은 C의 내용들이 남아 있는 분들이 있어 상대적으로 쉬운 편이다.


모든 컴파일러가 목적 코드(obj)를 만들 때, 소스코드의 변수, 함수, 클래스 등의 이름을 변형해 저장한다. 이를Name Mangling(일부 책에서는 이를 이름 규칙이라고 하는데 표현이 애매해 사용하지 않겠다.)이라고 한다. 안타깝게도, C 컴파일러는 C++ 컴파일러와는 다른 규칙을 가지고 있다. 따라서 이를 제대로 알아야 C 언어로 작성된 함수, 변수를 사용할 때 C++ 프로그램의 링킹을 제대로 이해할 수 있기 때문에 이 Mangling 규칙에 대해서 이야기를 하지 않을 수 없다. 


본론에 들어가기 앞서 참고할 사항들에 대해 잠깐 이야기를 해보자. 이 Mangling 규칙은 표준이 없기 때문에 컴파일러 종류(MS VC, GNU 등)에 따라 다르며, 이 글에서는 VC(Visual C++) 컴파일러를 기준으로 다뤘다. 또한, 아래 설명 과정에서서, 함수 명칭이라고 표현하지만 정확히 말해서는 심볼(symbol)로 보는 것이 옳다고 본다. 마지막으로, C와 C++의 차이를 인지하는 것에 초점을 두기 때문에 자세한 내용은 다루지 않았다.


Mangling 규칙 - C 컴파일러

C 컴파일러는 C 소스 코드를 컴파일해 obj를 만들 때 함수 이름 앞에 '_'를 붙이는데, 아래 예시와 같다.


int foo(int x, int y)

int main()


_foo

_main


C 컴파일러에서는 이 두 함수가 사용되는 C 소스 코드 모든 곳에서 이름을 변경 시켜 obj에 저장한다. C 컴파일러의 Mangling 규칙은 매개 변수나 리턴 타입은 전혀 반영하지 않는다는 점도 알아두자.








main.c / foo.c 소스 코드에 대해서 컴파일 및 링크 과정을 보면 위와 같이 진행된다.

함수 이름은 앞서 말했던 것처럼 '_함수명'으로 바뀐다. 여기서 눈여겨 봐야할 부분은 foo.c(foo함수만 있는 소스코드)에 있는 foo함수와 main.c(main함수가 있는 소스코드)에 있는 foo함수가 이름 규칙에 따라 '_foo'로 바뀐 상태로 obj 파일에 기록되고, main.obj / foo.obj가 링크될 때 main.obj에서 호출하는 함수 '_foo'가 foo.obj에서 찾아 호출하게끔 링킹이 이루어져 main.exe 파일이 생성된다는 점이다.


C 컴파일러의 Mangling 규칙이 이러하다는 걸 알면, 자연스레 C에서 함수 중복이 불가능 한 이유가 무엇인지 알게 된다. C 컴파일러의 Mangling 규칙을 따라가면 두 개 이상의 동일한 이름을 가진 함수를 만들 수 없고, 결국 컴파일/링크 과정에서 오류를 발생시키기 때문이다.




Mangling 규칙 - C++ 컴파일러

C++ 컴파일러는 C 컴파일러와 다르다. obj를 만들 때, 매개 변수의 유무/숫자/타입, 함수의 리턴 타입 등을 모두 고려한다. 오로지 이름만 고려하던 C 컴파일러와는 다르기 때문에 함수를 중복 작성(Overloading)해도 obj 내에서 구분할 수 있다.


int foo(int x, int y)

int foo(int x)

int foo()

int main()


?foo@@YAHHH@Z

?foo@@YAXH@Z

?foo@@YAHXZ
_main


C++ 컴파일러는 매개 변수, 리턴 타입 등에 근거해 서로 다른 이름으로 변형시켰다. 다만 예외가 존재하는데 바로 main함수다. main함수만은 C와 동일하게 '_main'으로 명명한다.








이처럼 두 언어의 컴파일러가 제시하는 Mangling 규칙이 다르기 때문에 C로 작성된 함수를 C++ 프로그램이 그냥 호출할 경우 링크 오류가 발생한다. 아래 그림을 보자. main.cpp를 컴파일하면 C++ 컴파일러에 의해 컴파일 되며, foo.c를 컴파일하면 C 컴파일러에 의해 컴파일되어 각기 다른 Mangling 규칙에 따라 생성된 obj파일이 생성되어 링크 오류가 발생한다.









이제 정상적인 링킹이 이루어지도록 하려면 어떻게 해야할지 알아보자. C++ 프로그램 상에서 C 소스를 끌어와 쓰는 것이니, C++ 컴파일러에게 특정 부분은 C 컴파일러의 Mangling 규칙을 준수하도록 하면 된다. 이런 지시를 하는 구문이 바로 extern "C"다.



extern "C" int foo(int x, int y);

extern "C"

{

   int foo(int x, int y);

   void voo();

   double doo(double x, double y);

}

extern "C"
{

   #include "CFunction.h"

}


함수가 여럿인 경우에는 위처럼 묶어서 선언도 가능해서 지시문을 여러 번 반복하지 않아도 된다. 아예 헤더 파일 자체가 C 컴파일러의 규칙을 따르게 하고 싶으면 자체를 통째로 지정하는 것도 가능하다. C++ 표준 라이브러리의 헤더 파일에서는 이러한 지시문으로 여러 가지 헤더 파일을 통째로 지정하기도 한다.






위 그림은 extern "C" 지시문을 이용해서 C++ 프로그램 상에서 C 함수를 호출 시 링크 문제가 발생하지 않게 만든 코드 예시를 보여준다. 이처럼 extern "C" 지시문을 이용하면, 특정 부분(foo함수) C 컴파일의 Mangling 규칙에 따라 obj파일을 기록하기 때문에 링커가 문제 없이 코드 내에서 함수를 찾아 링킹을 할 수 있다.



728x90
728x90