[C++] Name Mangling

ChangJin·2024년 12월 24일

C++

목록 보기
2/3
post-thumbnail

글의 목적

C++의 overloading을 탐구하던 중 C++ 함수 오버로딩이 내부적으로 어떻게 만들어지는지에 대해 학습하기 위함

알게 된 점

C++ 컴파일러에서는 Name Mangling을 통해 메타데이터에 고유한 값을 넣어서 모호성을 없앱니다. 그리고 컴파일러간에 표준화되지 않았기 때문에 다른 컴파일러에서 만들어진 Name Mangling 데이터를 연결하기가 매우 어렵습니다.

참고한 문서

내용

오버로딩을 보여주는 C++ 코드


#include <iostream>

int func(int a, int b)
{
    return a + b;
}

double func(double a, double b)
{
    return a + b;
}

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

위의 코드는 C++의 오버로딩을 보여주는 예시코드이고 잘 작동합니다.

함수의 선언을 앞으로 두기

#include <iostream>

int func(int a, int b);
double func(double a, double b);

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

int func(int a, int b)
{
    return a + b;
}

double func(double a, double b)
{
    return a + b;
}

위처럼 함수의 선언을 앞으로 두고 구현을 뒤에 둘 수 있습니다. 근데 여기서 만약 구현 부분을 삭제하면 어떤 오류가 나올지 궁금했습니다.

함수의 구현 부분을 삭제하기

#include <iostream>

int func(int a, int b);
double func(double a, double b);

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

위의 코드는 다음과 같은 오류가 발생합니다.

"int __cdecl func(int,int)" (?func@@YAHHH@Z)main 함수에서 참조되는 확인할 수 없는 외부 기호
"double __cdecl func(double,double)" (?func@@YANNN@Z)main 함수에서 참조되는 확인할 수 없는 외부 기호

이 오류를 살펴보면, func라는 함수 뒤에 @@YAHHH@Z, @@YANNN@Z라는 이름의 무언가가 있는 것을 볼 수 있습니다.

이를 검색해보니, C++ 컴파일시에 생기는 Name Mangling이라는 현상이었습니다. 이는 컴파일러로부터 링커로 함수, 클래스등의 정보를 전달할 때 고유한 다른 이름을 붙여주는 현상으로 이해했습니다.

위키백과에 따르면 void func(void) 함수 기준으로 ?h@@YAXXZ 라는 이름의 Name Mangling이 생긴다고 나와있는데 실제로 확인해봤습니다.

void voidfunc();

int main(int argc, char* argv[])
{
    voidfunc();
    return 0;
}

다음의 코드를 실행하면 다음과 같은 오류가 발생합니다. @@YAXXZ 라는 이름의 NameMangling이 생긴 것을 확인할 수 있습니다.

"void __cdecl voidfunc(void)" (?voidfunc@@YAXXZ)main 함수에서 참조되는 확인할 수 없는 외부 기호

C 스타일로 만들어주기

extern "C" int func(int a, int b);
double func(double a, double b);

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

다음처럼 extern 키워드를 사용하면 C 스타일로 만들 수 있습니다. extern에 대해서는 아래의 링크를 참고하시면 됩니다.

https://learn.microsoft.com/ko-kr/cpp/cpp/extern-cpp?view=msvc-170

이 코드를 실행하면 다음과 같은 오류가 발생합니다.

funcmain 함수에서 참조되는 확인할 수 없는 외부 기호
"double __cdecl func(double,double)" (?func@@YANNN@Z)main 함수에서 참조되는 확인할 수 없는 외부 기호

int를 반환하는 int func(int a, int b)은 Name Mangling이 표시되지 않는 것을 확인할 수 있습니다.

전부 C 스타일로 바꾸기

#include <iostream>

extern "C"
{
    int func(int a, int b);
    double func(double a, double b);
}

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

다음처럼 전부 C 스타일로 바꾸고 실행하면 어떻게 될까요?

'func': 'extern "C"' 연결을 사용하여 함수를 오버로딩할 수 없습니다.

위와 같은 오류가 발생합니다. 이는 C 에서는 오버로딩을 지원하지 않기 때문에 func 이라는 이름의 함수를 여러 개 만들 수 없다는 의미입니다.

따라서 다음처럼 바꾸어 주어야 합니다.

C 스타일 오버로딩 제거

#include <iostream>

extern "C"
{
    int func(int a, int b);
    double func_double(double a, double b);
}

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

int func(int a, int b)
{
    return a + b;
}

double func_double(double a, double b)
{
    return a + b;
}

위의 코드는 정상적으로 동작합니다. 그리고 만약 C 스타일과 같이 쓰면서 오버로딩도 가능하게 하고 싶다면 아래 코드처럼 작성해도 정상적으로 동작합니다.

#include <iostream>

extern "C"
{
    extern "C++"
        int func(int a, int b);
    double func_double(double a, double b);
}

int main(int argc, char* argv[])
{
    std::cout << func(1, 2) << std::endl;
    std::cout << func(1.2, 1.3) << std::endl;
    return 0;
}

int func(int a, int b)
{
    return a + b;
}

double func(double a, double b)
{
    return a + b;
}

C++ 에서 Name Mangling과 extern 사용 예

strchr을 사용해 문자열에서 특정 문자가 포함되어 있는지 확인하는 코드입니다.

int main(int argc, char* argv[])
{
    char s[5] = "ABCD";
    char* t = strchr(s, 'A');
    return 0;
}

string.h를 살펴보면 다음과 같은 부분을 볼 수 있습니다.

#ifdef __cplusplus
extern "C++"
{
    _Check_return_
    inline char* __CRTDECL strchr(_In_z_ char* const _String, _In_ int const _Ch)
    {
        return const_cast<char*>(strchr(static_cast<char const*>(_String), _Ch));
    }

    _Check_return_
    inline char* __CRTDECL strpbrk(_In_z_ char* const _String, _In_z_ char const* const _Control)
    {
        return const_cast<char*>(strpbrk(static_cast<char const*>(_String), _Control));
    }

    _Check_return_
    inline char* __CRTDECL strrchr(_In_z_ char* const _String, _In_ int const _Ch)
    {
        return const_cast<char*>(strrchr(static_cast<char const*>(_String), _Ch));
    }

    _Check_return_ _Ret_maybenull_
    inline char* __CRTDECL strstr(_In_z_ char* const _String, _In_z_ char const* const _SubString)
    {
        return const_cast<char*>(strstr(static_cast<char const*>(_String), _SubString));
    }
}
#endif // __cplusplus

extern "C++"을 써서 이를 C++ 스타일로 바꾸고 있습니다.
그리고 저 Check_return 같은 경우는 sal.h에 보면 다음과 같이 정의되어 있습니다.

// Check the return value of a function e.g. _Check_return_ ErrorCode Foo();
#define _Check_return_           _SAL2_Source_(_Check_return_, (), _Check_return_impl_)

// used with allocated but not yet initialized objects
#define _Ret_notnull_                       _SAL2_Source_(_Ret_notnull_, (), _Ret1_impl_(__notnull_impl))
#define _Ret_maybenull_                     _SAL2_Source_(_Ret_maybenull_, (), _Ret1_impl_(__maybenull_impl))
#define _Ret_null_                          _SAL2_Source_(_Ret_null_, (), _Ret1_impl_(__null_impl))
profile
게임 프로그래머

0개의 댓글