더 안전하게 C++ 코드를 작성하는 법

이 글에서 이야기하는 ‘안전한 코드’의 의미

적어도 빌드는 돼야지…
적어도 왕초보 실수는 없어야지…
적어도 돌리다가 터지진 않아야지…

적어도 이게 ‘작동하는 코드’인가 에 대한 내용이 주가 됩니다.

해킹 시도에 안전한지, 무적의 자율주행 알고리즘을 만든다던지와는 거리가 멉니다 ㅎㅎ

 

간단요약 / TLDR;

  • gcc/clang/MSVC 컴파일러 다 써서 빌드하세요
  • -Wall -Werror -Wpedantic -Werror 빌드 플래그 쓰세요
  • 유닛테스트 꼭 하세요
  • clang-tidy나 cppcheck 꼭 쓰세요
  • sanitizer 꼭 쓰세요
  • MISRA 적용도 고려해보세요

 


컴파일러 종류는 최소 2개를 사용하세요

PC에서 사용하는 C++ 컴파일러는 주로 3가지가 있습니다.

  • Visual Studio에서 지원하는 MSVC
  • Apple 진영이 많이 사용하는 Clang
  • GNU/Linux 진영이 많이 사용하는 gcc

저는 코드를 작성할 때 최소 2개의 컴파일러를 기준으로 빌드해보는 것을 추천합니다.

각각의 컴파일러들마다 경고/에러를 걸러주는 기준도 조금씩 다르고, 성능도 꽤 다르기 때문입니다.

우선 경고/에러에 대해서 이야기 해봅시다. gcc에서는 빌드가 잘 되는데, clang에서는 빌드가 안될 수도 있습니다. 이 경우에는 clang에서 경고/에러를 수정하면 보통 gcc/clang를 모두 통과하는 깔끔한 코드를 만들 수 있습니다. 왠만한 경우에서 컴파일러 두개 중 하나만 선택해야하는 경우는 없습니다.

다음은 성능에 대한 부분입니다. gcc로 빌드한 결과가 MSVC보다 10% 더 빠르다고 해봅시다. 동일한 작업을 수행하는데 더 빠른 결과를 내는 컴파일러를 쓰는게 더 좋은 선택일 겁니다. MSVC로만 빌드하고 있었다면 평생 얻지 못할 10% 성능개선입니다. 물론 이 부분은 코드의 안정성과는 거리가 먼 부분이긴 합니다.

물론 멀티 컴파일러 빌드에도 단점은 있습니다. 컴파일러의 개수마다 빌드를 돌려야하기 때문에 오래걸린다는 점인데요, 이 부분은 CI 서버에 다중 agent를 둬서 MSVC, clang, gcc를 모두 한번에 빌드하고 성능을 측정하게 자동화해주면 되는 부분입니다. 나는 중저가용 노트북 하나로 모든걸 다 해야하고 내일 모레가 데드라인이다, 하시는 분들에게만 gcc 원툴 테크트리를 추천드립니다.

 


모호한 코딩을 검수하는 컴파일러 플래그를 활성화하세요

gcc/clang 컴파일러에는 다음과 같은 컴파일러 경고 플래그를 설정할 수 있습니다. 경고 플래그는 모두 -W로 시작합니다.

  • -Wall
  • -Wextra
  • -Wpedantic
  • -Werror

-Wall은 왠만한 모호한 코딩들을 잡아내는 경고 플래그들을 활성화합니다. 이 기능을 키는 것만 해도 꽤나 많은 이슈를 잡을 수 있습니다. ‘all’이라는 이름만 보면 모든 경고 플래그를 활성화하는 것 같지만, 실제로는 얼마 몇개 없습니다 (링크).

여기에 -Wextra를 써서 모호한 코딩들을 더 잡아주는 extra 플래그도 활성화합니다.

-Wpedantic 플래그도 활성화해줍니다. ISO C++에서 요구하는 사항들을 빡빡하게 걸러내주는 플래그입니다. 대충 어디어디 사전에 따르면 pedantic의 의미는 아래와 같습니다. 좋은 코드만 짤 수 있다면 좀 pedantic 하면 어떤가요 ㅎㅎ

pedant는 형식주의와 정확성에 지나치게 관심이 있거나 과시하고 오만한 학습 표시를하는 사람입니다.

마지막으로 -Werror 플래그를 활성화 해줍니다. 이러면 모든 ‘경고’는 ‘에러’가 됩니다. 즉, 이제 당신은 이 경고를 해결하기 전 까지 프로그램을 쓸 수 없습니다. 빨리 가서 경고를 다 고치십시오 후후

MSVC에서도 비슷한 플래그들이 있습니다. 다만 저는 SLAM쟁이라 MSVC에는 큰 관심이 없으니 직접 찾아보시는걸 추천드립니다.

 


Unit test를 쓰세요

Unit test에 대해 알아보고, 직접 데모를 돌려보고 싶으시다면 링크를 따라가세요

TDD 찬양론이 아니라는 점 우선 먼저 밝힙니다. 하지만 ‘그 코드 잘 작동해?’ 라고 물어봤을 때 ‘음 잘 몰라, 아마 그럴걸?’라는 답변을 듣고 반기는 프로그래머는 없을 겁니다.

정말 정말 백보 물러나서 얘기하는건데, 적어도 하나의 테스트는 꼭 돌려주시길 바랍니다. 테스트도 안되고 도큐먼팅도 안된 코드를 사용해야하는 상황이 오면, 당신은 그날 저녁부터 이 상황으로부터 도망가기 위해 이직 준비를 시작하게 될겁니다. 그러다가 극심한 스트레스로 두통과 몸살이 동시에 와 침대에서 끙끙거리다가 ‘내 인생 왜 이 모양 이 꼴이냐’ 하며 눈물 한방울을 흘리게 될겁니다.

정말 코드에 자신이 있으려면, 작성하신 모든 함수 하나하나마다 유닛테스트를 만들어서 ‘내 코드는 주어진 시나리오에서 잘 작동합니다’ 라고 얘기할 수 있도록 합시다.

 


정적분석 (Static analysis)

정적분석은 소스코드를 분석해서 좋지 않은 코드가 있으면 알려주는 툴입니다.

Visual Studio에는 기본적으로 적용되어있는 기능이며, 위험한 코드를 짜게 되면 ‘이렇게 짜지 않을래?’하고 제안을 하는 친구입니다. VSCode에서는 이 기능이 자동으로 지원되지 않는데, 그러다보니 VSCode 유저들 중에 코드 퀄리티가 엉망이신 분들도 꽤 많습니다. 하지만 다 핑계입니다. clang-tidy 익스텐션 받으면 곧바로 정적 분석이 돌아가기 때문입니다. CLion IDE를 사용하면 기본 적용되어있습니다.

clang-tidy도 플래그를 추가로 적용해서 더 좋은 코드를 만들 수 있습니다. 아래는 제가 좋아하는 플래그들입니다.


  • 기본 체크
    • bugprone-*
  • 버그 잡이
    • clang-analyzer-*
  • 버그 잡이 2
    • concurrency-*
  • 동시성 프로그래밍 버그 잡이
    • cppcoreguidelines-*
  • C++ Core guideline 준수
    • hicpp-*
  • High integrity C++ 가이드라인 준수
    • linuxkernel-*
  • 리눅스 커널에 맞는 코드 준수
    • modernize-*
  • 모던 C++ 사용
    • openmp-*
  • OpenMP 사용 법 준수
    • performance-*
  • 성능 향상 제안
    • portability-*
  • 이식성 제안

이러한 체커도 하나만 쓰면 좀 아쉽습니다.

CppCheck라는 정적분석 툴도 좋으니, clang-tidy와 함께 돌립시다.

아래는 clang-tidy를 돌렸을 때 나오는 코드 제안들의 예시입니다 (출처: https://blog.wholetomato.com/2021/01/08/a-brief-introduction-to-clang-tidy-and-its-role-in-visual-assist/)

 


동적분석 (메모리 주소, 메모리 leak, 쓰레드 관리)

동적 분석은 실제로 코드가 돌아가면서 생기는 허점들을 잡아줄 수 있습니다.

가장 대표적인건 1. 메모리 주소, 2. 메모리 leak, 3. 쓰레드 관리 쪽 허점들입니다.

Google에서 만든 Sanitizer가 있습니다. 뭘 써야할지 모르겠을 때는 그냥 다 적용합시다.

 


MISRA

MISRA C++은 실제로 안전이 중요한 시스템에서 지켜져야하는 C++ 프로그래밍 가이드입니다.

프로토타입 단계에서 필요한건 아니지만, 제품으로 만들려고 하면 꼭 거쳐야합니다. 어차피 거칠거면 처음부터 오토체커를 사용해서 잘 짜면 되지 않을까요?

오토체커 방법은 두가지가 있습니다.

첫번째는 CLion IDE에서 지원하는 오토체커를 사용하는겁니다. CLion에서는 정적분석 툴로 MISRA 오토체커가 들어가있습니다. 하지만 그렇게 막 좋은 편은 아닙니다. 2022년 1월 16일 기준으로, 211개 체크 중 65개만 구현이 되어있습니다. 구현 상황은 링크에 잘 나와있습니다. 하지만 그래도 65개라도 돌아가는거 어디입니까 헛헛

물론 이 방법을 사용하려면 CLion IDE를 써야합니다. 유료이긴 하지만 성능이 빵빵하니 개인적으로 추천하는 IDE입니다. 학생이시라면 교육용 라이센스로, 직장인이라면 회사에 사달라고 하세요. 개인 사용은 선택에 맡기겠습니다.

두번째는 CppCheck 기반 오토체커를 쓰는겁니다. CppCheck는 clang-tidy와 같은 정적분석 툴인데, 이 안에도 MISRA 체커 기능이 있습니다. 대신 CLion에서 지원하는 것과는 다르게, 직접 MISRA 도큐먼트를 15 파운드 (약 3만원)으로 구매한 후 직접 설정해줘야하는게 매우 귀찮습니다.

제 코드는 원래 경고/에러 없이 깔끔했습니다만, CLion의 MISRA 체커를 키니까 안전하지 않은 코드라고 호되게 혼나는 모습을 아래 사진에 담았습니다. 노란줄은 경고, 빨간줄은 에러인데, 211개의 체크를 다 거치면 얼마나 고쳐야할지 모르겠네요 허허