c/c++ 그리고 네임 맹글링

1 분 소요

extern "C"

임베디드코드를 짜다 보면 대부분의 기본 라이브러리들은 c로 만들어져 있다. 유심히 코드를 살펴봤다면 extern "C" { ~ } 형태의 코드를 확인 할 수 있는데 여기서 다음과 같은 오류를 발견 할 수 있다. 필자는 c와 c++를 동시에 사용하면서 에러가 발생했다.
arm-none-eabi-g++ -o "f446re_io_controller.elf" @"objects.list"   -mcpu=cortex-m4 -T"/Users/colson/workspace/mp_gw_robot/stm32/f446re_io_controller/STM32F446RETX_FLASH.ld" --specs=nosys.specs -Wl,-Map="f446re_io_controller.map" -Wl,--gc-sections -static --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -u _printf_float -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group
/Applications/STM32CubeIDE.app/Contents/Eclipse/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.9-2020-q2-update.macos64_2.0.0.202105311346/tools/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/bin/ld: ./Src/main.o: in function `main':
/Users/colson/workspace/mp_gw_robot/stm32/f446re_io_controller/Debug/../Src/main.cpp:145: undefined reference to `test(int)'
collect2: error: ld returned 1 exit status
make: *** [makefile:80: f446re_io_controller.elf] Error 1
"make -j9 all" terminated with exit code 2. Build might be incomplete.
뭐 보자면, 난 test(int)라는 함수를 선언해서 사용하는데 컴파일러는 이게 없다고 한다. 에러가 발생하는 이유는 c++과 c는 함수명이나 전역변수를 simbol로 저장하는 방식이 다르기 때문이다. 간단하게 예를 들어 보면 다음 c와 c++로 선언된 함수가 있다. test1.h 헤더의 경우 g++로 컴파일 할 때 __cplusplus 가 define 되고 test함수가 extern "C"로 둘러싸이게 된다. 이때 컴파일러는 extern "C" 포함한 형태로 컴파일를 하게 된다.
test1.h
test2.h
코드는 요기 에 있다. CMakeLists.txt 로 cmake로 컴파일도 가능하니 참고! 각각을 gcc(c) 와 g++(cpp) 로 컴파일 해보자.

gcc

$ gcc -o test3 main.c -I./

g++

$ g++ -o test1 main.cpp -I./ -D TEST1
$ g++ -o test2 main.cpp -I./ -D TEST2

컴파일을 완료하면 루트 폴더에 test1, test2, test3 실행파일이 생성된다. 그럼 이 샐행파일에서 test 라는 함수의 심볼이 어떻게 생성되는지 보기위해 바이너리를 읽어보자. OSX 와 linux에서 바이너리를 다음과 깉이 읽을 수 있다.

OSX read binary file

$ nm $test1 | grep test

Linux read binary file

$ readelf $test1 | grep test 결과값은 아래와 같은데, test1은 extern "C"를 포함한 g++ 로 컴파일 하였기 때문에 test2와 심볼의 이름이 다르다. g++ 로 컴파일 한 경우는 __Z4testi 로 이름이 변경된 것을 볼 수 있는데 이는 함수의 오버로딩과 관련이 있다. 그리고 이렇게 이름을 컴파일러의 규칙으로 변경하는것을 Name Mangling이라 한다. g++(cpp) 로 컴파일 할 때, c기반의 헤더를 포함하고자 할 때 extern "C" { ~ } 로 묶어주면 사용 가능하다.

Name Mangling

Name Mangling은 사용하는 이유는 컴파일러가 각 코드를 컴파일하면서 함수 이름들을 변경할 때 각 파일마다 존재할 수 있는 동일한 함수명을 링크 할 때 구분하기 위해서다. 각각의 컴파일러는 함수에 대해서 함수의 이름, 파라미터, 콜링 컨벤션, 네임 스페이스 등을 사용하여 심볼 이름을 만들어낸다. 그리고 이름을 짓는 규칙은 각 컴파일러마다 다르며 제각각이다. 아래 표를보면 각 컴파일러마다 다른 심볼 규칙을 가지는 것을 볼 수 있다.

참고