C++ Insights – 컴파일러의 눈으로 소스 코드를 들여다보세요

C++ Insights – See your source code with the eyes of a Compiler

github.com/andreasfertig ▲ 161 댓글 32 rramadass

요약

C++ Insights는 컴파일러가 몰래 끼워 넣는 코드를 C++ 소스 형태로 펼쳐 보여 주는 도구입니다.

안드레아스 페르티히가 만든 오픈소스 프로젝트입니다. Clang(LLVM 기반 C/C++ 컴파일러)으로 코드를 분석한 뒤, 평소에는 보이지 않는 변환 결과를 다시 C++ 코드로 써 줍니다.

왜 중요한가

  • 컴파일러가 대신 써 주는 코드를 직접 보여 주므로, 문법 뒤에 숨은 동작을 추측하지 않고 확인할 수 있습니다.
  • 웹에서 설치 없이 쓰고 결과를 링크로 공유할 수 있어, 학습과 토론의 공통 자료로 쓰입니다.

핵심 내용

  • 컴파일러가 자동으로 만드는 생성자, 소멸자, 대입 연산자와 암시적 형 변환을 코드로 드러냅니다.
  • 람다는 클래스로, 범위 기반 for 문은 일반 for 문으로 바꿔 보여 줍니다.
  • auto와 decltype이 추론한 실제 타입, 구조적 바인딩의 풀어 쓴 형태도 보여 줍니다.
  • 소스 대 소스 변환(코드를 받아 코드를 내놓는 방식)이지만, 결과가 항상 컴파일되지는 않는다고 밝혔습니다.
  • C++11부터 C++23까지 지원하고 C++26 지원도 작업 중이며, 라이선스는 MIT입니다.

HN 반응

  • 람다와 코루틴을 이 도구로 이해했다는 경험담이 이어졌고, 람다가 사실상 클래스라는 점에 놀라는 이용자도 있었습니다.
  • 이런 도구가 필요할 만큼 C++이 복잡하다는 한탄에는, C의 -E나 Rust의 cargo expand처럼 다른 언어에도 비슷한 도구가 있다는 반론이 나왔습니다.

댓글

30개 표시 · 전체 32개
  1. alankarmisra HN

    오, 저 이런 거 정말 좋아해요. 저는 C++ -> Clang -> JS 트랜스파일러를 만들어서, HTML로 C++ 소스 위에 값이 바뀌는 상태를 보여 주고 사람들이 상태 변화 전체를 한 단계씩 따라가 볼 수 있게 했어요. 입력값을 바꿔서 값이 어떻게 달라지는지도 볼 수 있는데, JS 계층에서 값을 다시 계산하기 때문이에요. 제가 굳이 트랜스파일까지 한 이유가 바로 이거고요. 아니었다면 상태 변화만 출력해서 CSV로 파싱해 단계별로 보여 주면 됐을 테니까요. 재미있는 프로젝트였어요!

    https://youtu.be/ZPGysUX62OY

  2. jsrcout HN

    꼭 써 봐야겠네요. 특히 C++를 쓸 때면 “그래서 컴파일러는 지금 대체 뭘 생각하고 있는 거지?” 싶은 문제가 늘 있었거든요.

  3. rramadass HN

    이 도구는 여기서 볼 수 있습니다 - https://cppinsights.io/

  4. StilesCrisis HN

    readme 예제에 람다 캡처가 나왔으면 좋았을 텐데요. 컴파일러 마법이 진짜로 드러나는 곳이 바로 거기거든요.

  5. sltkr HN

    C++ 람다는 operator()를 오버로드한 구조체일 뿐입니다. 이걸 알고 나면 훨씬 덜 신비롭게 느껴지죠. 이런 걸 배우는 데 이 도구가 얼마나 유용한지 보여 주는 좋은 예이기도 합니다(물론 C++ 표준을 읽어도 되겠지만, 그럴 시간이 누구에게 있겠습니까?).

    예시는 이렇습니다: https://cppinsights.io/s/f04b6896

    (이 예시는 C++의 범위 기반 for 루프가 어떻게 동작하는지도 보여 줍니다. 이것 역시 꽤 평범한 for 루프에 얹은 문법적 설탕(syntactic sugar)일 뿐이지만, 세부 사항이 흥미롭습니다. 일반적인 for 루프와 달리 루프 변수가 루프 본문 안에서 선언되거든요.)

  6. Someone HN

    이 예시는 C++의 범위 기반 for 루프가 어떻게 동작하는지도 보여 줍니다. 이것 역시 꽤 평범한 for 루프에 얹은 문법적 설탕일 뿐이지만, 세부 사항이 흥미롭습니다. 일반적인 for 루프와 달리 루프 변수가 루프 본문 안에서 선언됩니다

    C++에서는 그렇지 않은 것 아닌가요? 거기서는 이 코드가 컴파일되지 않습니다

      for(int i = 0; i < 3; ++i)
      {}
      i = i + 1;
    

    ( https://cppinsights.io/s/26405687 : error: use of undeclared identifier 'i')

    https://en.cppreference.com/cpp/language/for :

    *“for 문은 다음과 동등합니다:

      {
        init-statement
        while ( condition )
        {
          statement
          expression ;
        }
      }
    

    단, […]라는 점은 다릅니다.”*

    그리고

    *“C에서는 init-statement와 condition의 스코프에서 선언된 이름을 statement의 스코프에서 가릴(shadow) 수 있지만, C++에서는 금지되어 있습니다:

      for (int i = 0;;)
      {
        long i = 1;   // valid C, invalid C++
        // ...
      }
    

    “*

  7. StilesCrisis HN

    저는 람다 _캡처_를 보여 줬으면 좋겠다고 한 겁니다. 컴파일러가 스코프 경계를 넘나드는 변수가 무엇인지 자동으로 판단하고, 그 변수마다 코드를 끼워 넣는 과정이 들어 있으니까요.

  8. rramadass HN

    여기 있는 도구에 샘플 코드를 직접 입력해 보면 볼 수 있습니다 - https://news.ycombinator.com/item?id=49928362

  9. mrlonglong HN

    저도 같은 생각입니다.

  10. rramadass HN
  11. mrlonglong HN

    람다가 그냥 클래스라고요!? 전혀 몰랐네요.

  12. WalterBright HN

    D에서 람다는 그냥 중첩 함수입니다:

        int foo(int i) {
            int add(int x) { return i + x; } // nested function
            return add(3);
        }
    
    int bar(int i {
            alias lambda = (x) => x + i;  // lambda
            return lambda(3);
        }
        }
    
  13. f1shy HN

    이 도구가 마음에 들긴 하는데도, 슬픈 것 같기도 하고 화나고 짜증 나고 실망스러운 것 같기도 한, 뭐라고 딱 표현하기 어려운 묘한 기분이 드는 사람은 저뿐일까요. 언어에 이런 도구가 필요하다는 게(아니면 적어도 쓸모가 크다는 게) 말이에요.

    제 코드를 처음 읽었을 때의 해석이 뭔지가 분명하게 드러났으면 좋겠다는 거예요. 고급 최적화나 컴파일러의 세세한 잔재주 얘기가 아니라 언어 자체의 얘기예요. 이 도구는 어떤 C++ 컴파일러에서든 쓸모가 있잖아요. 어쩌다 이렇게 복잡한 언어가 된 걸까요?

    그러니까 제 말은(아주 개인적인 생각이라 죄송해요), 저한테 프로그래밍 언어는 문제와 그 해법을 생각하도록 돕는 도구예요. LLM 시대에는 더더욱 기계에게 뭔가를 시키는 도구가 아니라, 수학처럼 아이디어와 문제와 해법을 형식화하는 언어고요. 변호사가 아니면 이해할 수 없을 만큼 난해해서는 안 되잖아요, 그렇죠? 아니면 제가 너무 늙어서 생각이 완전히 엇나간 걸까요?

  14. jcelerier HN

    그럼 어떤 언어는 이런 도구가 필요 없다고 보시나요? 저는 인터프리터 언어나 JIT 언어를 쓸 때마다 이런 도구가 있었으면 싶거든요. 지금 일어나는 일이 정확히 어떤 의미론(semantics)에 대응되는지, 각 프런트엔드가 그걸 어떻게 최적화하는지 분명히 알고 싶어서요. JavaScript의 "a" + "b" 같은 단순한 것도 그래요. 모든 JS 런타임이 이걸 어떻게 처리하는지 확실히 아세요? 파싱할 때 최적화로 문자열을 미리 합쳐 버릴까요? 더 큰 네이티브 문자열 타입을 미리 할당해 놓고 이어 붙일까요? 아니면 결국 std::u16string("a") + std::u16string("b")와 같은 일을 할까요?

  15. kccqzy HN

    아니요, 언어에 그 도구가 필요한 건 아닙니다. 언어를 처음 접하거나 특정 언어 기능을 처음 접하는 사람을 위한 교육용 도구입니다. 연습하다 보면 언어 기능은 저절로 몸에 배어서, 문제를 생각할 때 따로 의식하지 않게 되고요.

    README 예제에서도 C++에 익숙한 사람은 POD 타입에 자동으로 만들어지는 복사 생성자 같은 건 신경 쓰지 않습니다. 대입이 operator=로 디슈가(desugar)된다는 사실이나, 대입에 static_cast가 끼어들 수 있다는 사실도 마찬가지고요.

  16. f1shy HN

    이런 도구가 필요하다(아니면 적어도 쓸모가 크다)

    물론 그 도구가 필요 없는 사람도 있죠. 요점은 다른 언어는 이런 설명 도구가 아예 필요 없고, 작은 책 한 권만 읽어도 충분하다는 거예요. 저는 C++도 정말로 이런 게 필요 없었으면 좋겠지만, 말씀드렸듯 아마 “저 혼자”만 그런가 봐요.

  17. kccqzy HN

    다른 언어는 이런 설명 도구가 아예 필요 없고

    평범한 C에는 매크로를 펼쳐 주는 -E가 있습니다. Rust에는 cargo expand가, Haskell에는 -ddump-simpl이, Common Lisp에는 macroexpand가 있고요. 문법이 복잡하거나 사용자가 문법을 입맛대로 바꿀 수 있는 언어라면 어디든 이런 도구가 필요합니다. 그런 도구가 실제로 존재하느냐는 그 언어의 인기와 커뮤니티의 요구를 반영할 뿐입니다.

  18. drysine HN

    다른 언어는 이런 설명 도구가 아예 필요 없고

    정말 그런가요?

  19. esikich HN

    컴파일이 간단한 언어가 대체 어디 있나요? 컴파일 언어가 마음에 안 드시는 건가요? 뭘 불평하시는 건지 잘 모르겠네요.

  20. f1shy HN

    Lisp는 컴파일하기 간단해요. 이 도구가 다루는 수준에서라면 C도 기본적인 편이고, 아마 다른 100개쯤 되는 언어도 그럴 거예요(다시 말하지만 컴파일러 전체가 아니라 이 도구가 보여 주는 부분만요).

    컴파일 자체는 어렵지 않아요. 오류 복구를 잘 하고, 오류가 났을 때 좋은 피드백을 주고, 최적화까지 하는 게 어려운 거죠.

    이 도구는 그런 부분은 알려 주지 않아요(그건 godbolt가 하죠). 이건 해석의 첫 번째 층이 어떻게 동작하는지에 대한 도구예요. 컴파일러를 만들려는 게 아니라, 코드가 실제로 무엇을 뜻하는지 이해하려는 거죠. 한눈에 봐서는 분명하지 않으니까요.

  21. dasyatidprime HN

    “C++는 지나치게 복잡하고, 움직이는 부분이 많고, 뒤에서 암묵적으로 일어나는 일이 너무 많아서 이해하기 어렵다”는 큰 취지에는 동의합니다. 다만 Lisp 환경에도 매크로 확장 스테퍼(macro expansion stepper)가 있는 데는 다 이유가 있다는 점만 잠깐 짚고 싶습니다.

  22. pjmlp HN

    고전 Lisp 1.5라면 그렇죠. 현대의 Lisp는 그렇지 않고, 그래서 Lisp In Small Pieces 같은 유명한 책들이 나온 겁니다.

    C도 K&R C나 초기 C89/90 컴파일러, 그러니까 확장 기능이 폭발하고 새 언어 표준과 현대 ISA가 나오기 전 얘기라면 몰라도요.

  23. rramadass HN

    언어와 도구를 모두 오해하고 계십니다.

    모든 언어의 컴파일러에는 소스 언어의 추상화 수준을 높은 수준에서 낮은 수준으로 “낮추는(lowering)” 개념이 있습니다. 작성한 소스 -> 더 단순한 소스 -> AST -> IR -> 기계어 순이죠. 그중 하나가 “디슈가링(desugaring)”, 즉 복잡한 문법을 더 단순한 문법으로 바꿔서 이후 컴파일 단계가 더 작고 다루기 쉬운 언어 부분집합만 처리하게 하는 것입니다.

    이 도구는 AST에서 소스를 다시 생성하는 방식으로, C++ 소스 언어 안에서 일어나는 디슈가링과 그 밖의 로워링(암시적 변환, 타입 추론 등)을 모두 보여 줍니다. 그래서 컴파일 과정의 해당 단계까지를 온전히 들여다볼 수 있습니다.

    PS: 작성자가 직접 설명하는 영상 발표 - https://www.youtube.com/watch?v=VJ6ZvDRYzNE&t=558s

  24. rs545837 HN

    C++에만 국한된 얘기는 아니라고 봅니다. Python 데코레이터, Rust 매크로 등 거의 모든 언어의 컴파일러가 우리 눈에 보이지 않는 코드를 만들어 내니까요. C++는 그런 게 더 많고 예측하기도 더 어려울 뿐입니다. 확장된 버전을 볼 수 있다는 건 이런 일을 하는 어떤 언어에서든 유용합니다.

  25. veexx103 HN

    저도 비슷한 걸 만들어 봤는데, Clang AST로 복원한 코드가 여러 문제 때문에 컴파일이 안 되더라고요.

    이런 문제들은 해결할 수 있을까요?

  26. kvemkon HN

    소스 코드를 보세요 ...

    제가 보고 싶은 건 C++ 코드 블록마다(가능한 한 잘게 쪼개서) 컴파일에 시간이 얼마나 걸리는지예요.

  27. kizxc4395 HN

    정말 흥미롭네요.

  28. IshKebab HN

    저는 이 도구 덕분에 람다를 이해했어요. 정말 훌륭한 도구예요.

  29. drysine HN

    C++ 코루틴에도 좋아요.

  30. pjmlp HN

    C++20 코루틴은 .NET의 동작 방식과 awaitable 타입의 관련 매직 메서드에서 영감을 받은 것이라, .NET 모델을 알아 두면 도움이 됩니다.

Hacker News에서 보기 ↗