내가 좋아하지 않는 C/C++의 레거시 by 김민장

C 언어는 컴퓨팅 파워가 매우 척박했던 시절에 만들어진 언어였다. 이후 C99에 이르러 상당히 많은 진화를 하긴 했지만, 워낙 옛날부터 널리 쓰였기에 아무리 발전을 하여도 여전히 적지 않은 레거시(legacy)가 많은 이들에게 고통을 주고 있다. 하위 호환성 유지라는 이유도 있지만 솔직히 “귀찮다. 그냥 냅두자.”와 같은 귀차니즘도 분명 큰 이유다. C의 최대 레거시라면 단연 문자열 형의 부재를 꼽고 싶다. 하지만 C 언어가 나왔을 당시를 생각하면 문자열의 길이를 저장할 공간 조차 배부른 사치였으므로 이해는 한다. 그 다음으로 꼽고 싶은 것은 복잡한 컴파일/빌드 과정과 전처리기이다. 이건 C++에도 고스란히 적용된다. 오늘은 여기에 대한 잡생각.

   1: // Defined getFoo()
   2: #include "foo.h"
   3:  
   4: extern int getMagic();
   5:  
   6: int main(void) {
   7: #ifdef SUPER_MAGIC
   8:   return getMagic();
   9: #else
  10:   return getFoo();
  11: #endif
  12: }

썰렁한 위의 C/C++ 코드를 보고 어떻게 실행될 것인지 예측할 수 있을까?

잠시 딴 소리를 해보자면, C/C++ 언어는 정적 혹은 렉시컬 스코핑(lexical scoping) 언어라서 소스 코드만 보면 이 변수가 어디서 값을 가져 오는지 대부분 알 수 있고 고로 프로그램의 실행 결과도 일반적으로 예측 가능하다. 반대 개념이 동적 스코핑인데, 이를 지원하는 언어는 변수 값의 결정이 실행 시간의 함수 호출 문맥에 달려있다. 그래서 컴파일 시간에는 그 값의 예측이 어렵다. 물론, 지금 이 썰렁한 코드는 정적/동적 스코핑과 아무런 상관이 없다. 그런데 아무리 정적 스코핑인 언어라 하더라도 이 코드만 봐서는 수행을 예측하기 어렵다. 정적 스코핑의 장점을 고스란히 날려버리는 것이다.


코드 해석을 어렵게 하는 레거시 요소를 나열하면 다음과 같다:

(1) 라인 2: “foo.h”가 어디서 오는지 명확히 알 수 없다. 아주 초보자 시절에는 “따옴표는 현재 디렉토리에서 파일을 찾는다”라고 배웠으니 foo.h 파일이 지금 이 소스 파일과 같은 디렉토리에 있을 것이라고 예측할 수 있다. 하지만 현실은 이러하지 않다는 걸 잘 안다. 컴파일러 –I 옵션으로 검색할 #include 디렉토리 목록을 따로 지정할 수 있다. 예를 들어, “foo.h”가 foo라는 디렉토리 밑에 있을 수도 있고 goo라는 디렉토리 밑에 있을 수 있다. 극단적인 예로, “foo.h” 파일이 다른 이름의 디렉토리 밑에 모두 있고, 컴파일러 옵션에서 –I 의 값을 바꿈으로써 다르게 인쿠르드 되는 것도 가능하다. 실제로 이런 경우를 봤다! 더 난해한 경우도 있다. 예를 들어, “foo.h” 파일이 어떤 파서에 의해 빌드시 생성되는 부산물일 수 있다. 그리고 이 파일의 위치는 소스 트리가 아니라 빌드 디렉토리 내에 있을 수 있다. 실제로 이런 경우가 있다. 그러면 아무리 “foo.h”를 소스 트리 내에서 뒤져도 안 나온다.

/src/foo/foo.h
/src/goo/foo.h
/build/generated_code/foo.h

(2) 라인 4: getMagic이 다른 곳에 정의가 되어있다. 링크 시간에 이 정의를 찾아야 하는데 어떤 오브젝트 파일 또는 라이브러리에서 참조해야 하는지 알 수가 없다. 역시 컴파일/링킹 시 옵션에 따라 라이브러리를 바꿔치기 하면 다른 결과가 나올 수 있다. (동적 라이브러리면 더 복잡하겠지만)

(3) 라인 8이 실행될지 라인 10이 실행될지는 SUPER_MAGIC 이라는 #define 값에 의존적이다. 이 소스 코드만 봐서는 SUPER_MAGIC이 정의 되는지 알기 어렵다. 일단, “foo.h”와 “foo.h”가 다시 포함하는 모든 파일을 뒤져야 한다. 있으면 다행인데 없어도 얼마든지 Makefile에서 –D 컴파일러 옵션으로 지정될 수 있다.

이렇게 소스 코드에서 보이지 않는 컴파일링/빌딩 과정에서 코드 해석에 영향을 주는 변수가 많다. 요약하면:

  • -I 로 주어지는 #include 탐색 폴더 목록
  • -D 로 주어지는 각종 #define 값들
  • 링크될 라이브러리

약간 부연 설명하면 이러하다.

-I 옵션은 생각보다 매우 남발된다. 대규모의 프로젝트는 소스 파일이 디렉토리 구조로 이루어져있다. 그런데 많은 곳에서 귀찮으니 각 폴더를 –I 로 해버리고 파일 이름만 #include에 적곤 한다. 이러면 쓰는 사람은 디렉토리 구조를 안 적어도 되는 약간의 편리함이 있다. 또, “../../” 같은 지저분한 상대 경로를 피할 수 있는 장점도 있다. 하지만 처음 접하는 사람에겐 당장 이 헤더 파일이 어디에 있는지부터 찾아야 한다. 당연히 편집기에서 이 파일을 바로 열 수도 없다.

위에서 언급한 –I, –D, 라이브러리 목록은 주로 Makefile에서 정의 된다. 문제는 이 Makefile이 매우 복잡할 수 있다는 것이다.

간단히 하나의 Makefile만 있으면 모르겠는데, Makefile.common 부터 시작해, 복잡한 디렉토리 구조마다 각각 자식 Makefile이 있다. Makefile의 호출 역시 “make FOO=true” 처럼 주어질 수 있다. 그러면 이 FOO 변수에 따라 또 복잡하게 $(CXX_FLAGS) 같은 값이 변경될 수 있다. 그래서 최종적으로 특정 파일이 어떤 컴파일 옵션으로 빌드 되는지 쉽게 알 수 없다. 특히, 남이 만들어 놓은 복잡하고 지저분한 Makefile의 해독은 암호 수준이다. 결국 Makefile을 돌려보면서 무수한 출력 더미 속에서 찾아 내기 일쑤다. 예를 들어, LLVM의 한 소스에 주어지는 컴파일 옵션은 이러하다. (천만 다행으로 Visual Studio에서는 매우 간편히 각 소스 파일이 최종적으로 받는 모든 컴파일 옵션을 볼 수 있다.)

/MP /we"4238" /we"4239" /GS /TP /W3 /wd"4065" /wd"4146" /wd"4180"/wd"4181"
/wd"4351" /wd"4355" /wd"4503" /wd"4551" /wd"4624" /wd"4715" /wd"4800"
/Zc:wchar_t /I"C:/Users/minjang/Downloads/build/lib/Analysis"
/I"C:/Users/minjang/Downloads/llvm-3.2.src/llvm-3.2.src/lib/Analysis"
/I"C:/Users/minjang/Downloads/build/include"
/I"C:/Users/minjang/Downloads/llvm-3.2.src/llvm-3.2.src/include"
/Zi /Gm- /Od /Ob0 /Fd"C:/Users/minjang/Downloads/build/lib/Debug/LLVMAnalysis.pdb"
/fp:precise /D "WIN32" /D "_WINDOWS" /D "_DEBUG" /D "_VARIADIC_MAX=10"
/D "_CRT_SECURE_NO_DEPRECATE" /D "_CRT_SECURE_NO_WARNINGS" /D "_CRT_NONSTDC_NO_DEPRECATE"
/D "_CRT_NONSTDC_NO_WARNINGS" /D "_SCL_SECURE_NO_DEPRECATE"
/D "_SCL_SECURE_NO_WARNINGS" /D "__STDC_CONSTANT_MACROS" /D "__STDC_FORMAT_MACROS"
/D "__STDC_LIMIT_MACROS" /D "_HAS_EXCEPTIONS=0" /D "CMAKE_INTDIR=\"Debug\""
/D "_MBCS" /errorReport:prompt /WX- /Zc:forScope /RTC1 /GR- /Gd /MDd /Fa"Debug"
/nologo /Fo"LLVMAnalysis.dir\Debug\" /Fp"LLVMAnalysis.dir\Debug\LLVMAnalysis.pch"

사실 분석하면 별 거 아니긴 하다. 하지만 이런 미세한 옵션 하나하나가 프로그램 결과에 영향을 줄 수도 있다.

그래도 이건 양반이다. Makefile? 까짓 것 그냥 하나하나 한줄한줄 읽어가며 머릿 속으로 빌드하면 된다. 그런데 안드로이드의 복잡한 빌드 시스템이라면? 실제 이런 걸로 엄청 고생하기도 했다. 안드로이드 빌드 파일(.mk)에서 –D 옵션 설정이 내 예상과 달리 작동해 엄청 고생했다. 더 이상의 자세한 설명은 생략 한다… (CMake도 플랫폼 독립적으로 기술한다는 장점이 있지만 Makefile이 가지는 어려움을 본질적으로 감추지는 못한다고 생각한다.)

 

그래서 해결책은?

이런 문제는 결국 C/C++의 Makefile 기반의 빌드 시스템과 전처리기가 굉장히 낡았기 때문이다. 거기에 익숙한 개발자들은 이런 걸 고치려 하지 않는다. 그렇지만 최대한 이런 어려움을 줄일 수 있는 방법은 분명 있다. 기본 철학은 이러하다.

소스 코드에 최대한 많이 빌딩(컴파일/링킹) 정보를 유추할 수 있도록 하여 소스 하나만 봐도 쉽게 이해할 수 있도록 한다.

(1) –I 는 최소한으로 한다.

-I 를 전혀 쓰지 않으면 언급했듯이 “../../” 같은 상대 경로를 써야 하므로 오히려 지저분해진다. 그래서 대안으로 소스 프로젝트의 루트 위치, 혹은 대표 상위 몇 개만 최소한으로 지정한다. 그리고 헤더 파일은 그 루트를 시작으로 디렉토리 구조를 다 적어가며 쓴다. 좋은 예로 페이스북의 HipHop PHP 컴파일러를 들 수 있다. 소스 파일 하나를 살펴보면:

   1: #include <compiler/construct.h>
   2: #include <compiler/parser/parser.h>
   3: #include <util/util.h>
   4:  
   5: #include <compiler/analysis/file_scope.h>
   6: #include <compiler/analysis/function_scope.h>
   7: #include <compiler/analysis/class_scope.h>
   8: #include <compiler/analysis/analysis_result.h>
   9: #include <compiler/analysis/ast_walker.h>
  10:  
  11: #include <compiler/statement/function_statement.h>
  12:  
  13: #include <compiler/expression/simple_function_call.h>
  14: #include <compiler/expression/simple_variable.h>
  15: #include <compiler/expression/closure_expression.h>
  16: #include <iomanip>
  17:  
  18: using namespace HPHP;
  19:  
  20: ///////////////////////////////////////////////////////////////////////////////
  21:  
  22: Construct::Construct(BlockScopePtr scope, LocationPtr loc)
  23:     : m_blockScope(scope), m_flagsVal(0), m_loc(loc),
  24:       m_containedEffects(0), m_effectsTag(0) {
  25: }

보다시피 상당히 깔끔하다. 실제 소스 폴더 구조는 hphp 라는 루트 폴더 아래에 compiler, utils, hhvm 등이 있다. compiler 아래에는 다시 analysis, statement 등이 있다. #include 만 봐도 바로 위치를 알 수 있고 어떤 목적으로 포함했는지도 쉽게 유추할 수 있다.

부디 복잡한 폴더 구조가 있음에도 -I로 다 때려 넣고 달랑 헤더 파일 이름만 적는 만행은 하지 말자.

 

(2) Makefile에서 정의되는 –D 역시 최소한으로 한다. 아, 그냥 #ifdef 자체를 줄이는 것이 좋다.

C/C++은 컴파일러가 묵시적으로 포함하는 #define 값이 있다. 예를 들어, GCC의 C 컴파일러는 이런 값을 미리 정의하고 있다. 이런 것은 어쩔 수 없다고 치자. 또, 상당히 범용으로 쓰이는 일부 직관적인 #define 값은 소스 파일 외부에서 정의되어도 큰 어려움이 없다. 대표적인 녀석들로 NDEBUG, DEBUG, ASSERT 같은 걸 들 수 있다. 그런데 그 외의 값은 정말 Makefile에서 마구 정의하는 것을 줄이도록 해보자.

보통 Makefile에는 여러 빌드 구성(configuration)이 있고 여기에 따라 –D 값이 바뀌는 것이 일반적이므로 Makefile에서 –D를 하는 것은 사실 자연스럽다. 하지만 최상위 값 하나, 또는 최소한만 –D로 넘어오도록 하고 영향을 받는 나머지 값들은 얼마든지 소스 코드 내에서 정의되도록 할 수 있다. 예를 들어, Makefile에서는 BUILD_FOO, BUILD_GOO 같은 것만 내려 보낸다. 그리고 이 값으로 globalDefines.h 같은 곳에 필요한 값이 정의되도록 한다.

그런데 무엇보다 아예 #define 값에 의존적인 조건부 컴파일 자체를 최소화 해야 한다. 하지만 옛날 코드를 보면 쓰이는 곳이 많다. 또, 코드 크기와 성능 문제로 조건부 컴파일을 어쩔 수 없이 해야 할 때가 여전히 빈번하다.

 

(3) 링크될 정보 역시 소스 파일에 기록하자. 안 되면 주석이라도 달자.

어떤 함수를 외부에서 끌어다 쓸 때 별도의 링크할 라이브러리가 필요하면 어떻게 해서든 그걸 소스 파일에 드러내는 것이 좋다고 생각한다. 사실 이건 빈번한 경우가 아니니 사소하다고 볼 수 있지만 거대한 프로젝트에서 발생되는 외부 심볼 못 찾는 링크 에러는 시간 잡아 먹기 좋은 레거시다. 윈도우에서 코딩할 때는 프로젝트 옵션 (일종의 Makefile)에 같이 링킹할 .lib 목록을 적지 않고 소스 코드에 바로 적는 방법을 썼다:

#pragma comment(lib, “foo.lib”) 

이 방법은 비표준 방법이라 이식성이 없다. MSVC는 #pragma comment 기능으로 링크할 라이브러리를 지정할 수 있다. GCC 대응은 안 찾아봤다.

비슷한 예로 특정 소스 파일에서 링커 최적화 옵션을 조작하고 싶을 때가 있었다. COMDAT folding 이라는 링커 최적화를 반드시 꺼야 할 때가 있었다. 역시 나는 프로젝트 세팅에 기입하지 않고 #pragma comment(linker, …)을 활용하였다.

 

정리

사실 상 모든 큰 프로젝트의 C/C++ 소스 파일은 파일 하나만 봐서는 어떻게 컴파일 되는지, 어떻게 작동되는지 알기 매우 어렵다. 나는 최대한 많은 정보를 소스 파일에 적었으면 한다. 그래서 정말 궁극적으로는 파일 하나만 에디터에 올려도 코드 분석과 컴파일(오브젝트 파일로)이 되는 정도가 되면 좋겠다.

하지만… 이런 일은 10년이 지나도, 20년이 지나도 이뤄지지 않을 것 같다. 세상은 터치에, 레티나에, 이제는 눈동자의 움직임도 인식하는 세상인데, 여전히 이곳은 구닥다리 커맨드 라인 인터페이스가 지배하고 있으니…

p.s. C#이나 다른 고급 언어는 어떠한지는 잘 몰라서 비교 분석을 하지 못한 것이 아쉽다. 그래도 설마 C/C++ 만큼이나 지저분할까.


덧글

  • 김지영 2013/03/21 17:29 # 삭제 답글

    C#은 잘 모르지만, 자바의 경우는:

    * preprocessor 없음.
    * namespace 는 디렉토리 구조랑 같음. /foo/bar/baz.java 라는 파일이 있으면 이 파일의 fully-qualified name 은 foo.bar.baz 가 됨.
    * namespace 는 (단채 이름 거꾸로) + alpha 를 쓰기를 권장하고 있음. 그래서 구글에 있는 모든 자바 파일은 "com.google..." 로 시작하고, 아파치 오픈소스 프로젝트들은 "org.apache..." 로 시작하고 등등. 장점은 namespace conflict 가 거의 없지만, 단점은 IDE 없이는 거의 프로그래밍을 못할 정도로 fully-qualified name 들이 길어짐.

    파이썬의 경우는
    * namespace 는 디렉토리 구조랑 같음.
    * preprocessor 없음.
    * module importing 은 결과만 보면 C의 #include 랑 비슷하지만.
    * 모든 파이썬 파일은 각각의 namespace 에서 실행됨.
    * module importing is idempotent - #ifndef guard 가 자동으로 된것이랑 똑같음.

    자바스크립트 (browser environment) 의 경우는:
    * preprocessor 없음.
    * namespace 없음.
    * 모듈 개념이 없음.

    개인적으로는:

    * 고급 언어에서 namespace는 필수라고 생각함. 그래서 자바스크립트로 프로그래밍하기가 참 고통스러움. 그래도 function closure 가 자바스크립트에서는 가능해서 이런식으로 access control 이 가능함.
    var exported = {};
    (function() {
    // 이 안에서 선언된 모든 변수들은 function scope.
    // 만약 모듈 밖으로 export 하고 싶다면 export 변수를 사용.
    var three = 3;
    exported.addThree = function(x) { return x + three };
    })();
    console.log(three); // "undefined"
    console.log(exported.addThree(5)) // 8

    하지만 결국 이것도 프로그래머 맘이고 모든 사람들이 다른방식으로 해서 꽝.

    * 자바가 빌드가 간단하다고는 하지만, 프로젝트가 커질수록 preprocessor, pre-compiler 등둥이 사용되는것은 필연적인데, (antlr, javacc, protocol buffer, 등등) 보통 이런것들이 자바 빌드 시스템들 (ant, maven, 등등) 에서 그렇게 잘 지원되지 않음. IDE 지원도 약간 허술...
  • 김지영 2013/03/21 17:32 # 삭제 답글

    아, 그리고 자바는 컴파일 옵션보다는 자바 가상 머신 옵션으로 퍼포먼스 튜닝을 하기 때문에 컴파일은 간단하지만, 실행시에 -XX:FOO... 같은 옵션들이 덕지덕지 붙습니다.
  • 김민장 2013/03/22 02:35 #

    긴 댓글 아주 감사합니다. 저도 테스트 코드는 자바로 작성하고 VM을 만들고 있는데 org.*** 이렇게 시작을 하더라고요. 말씀대로 C#이나 자바는 VM에서 JIT이 될 때 최적화가 되므로 바이트코드로 컴파일할 때는 사실상 최적화가 거의 없습니다. 대신 말씀대로 VM 실행 시 별도의 옵션이 붙죠 ㅎㅎ
  • 몽몽이 2013/03/21 22:13 # 답글

    뭐... 문제는 문제지만 extern만 최소화해도 그렇게 난처하지는 않을 것 같네요.
    개인적으로 남의 소스 보다가도 extern이 나오면 어떻게든 #include로 고쳐놓습니다. 물론 정말 불가피한 경우가 있긴 하지만...
  • 김민장 2013/03/23 04:15 #

    그래도 간단한 메서드 한 두개 참조할 땐 extern도 나름 유용할 수 있다는...
  • 채널 2nd™ 2013/03/22 01:07 # 답글

    현재의 윈도우즈 시스템이 이렇게 비대해진 것도 LEGACY 때문이라지요.

    그깟 LEGACY 귀찮으면(?) 판 뒤집고, COBOL로 옮겨 타면 되지 않을까.......... ㅎㅎ ;;; <-- 안드로이드는 가볍지요. LEGACY가 생기기 전(?)일테니.
  • 김민장 2013/03/23 04:15 #

    가상화, 동적변환 등으로 레거시를 최대한 가려야 한다고 생각합니다...
  • xwings 2013/03/22 08:13 # 답글

    그래도 eclipse 같은 개발환경에서는 경로에 대한 추적이 그나마 쉽지 않나요? 꽤 잘 찾아주던데...
  • 김민장 2013/03/22 15:28 #

    말씀대로 IDE에 컴파일이 되도록해서 올리면 -I 분석해서 #include 파일 잘 열어 줍니다. VS도 잘 되고요. 제가 원하는 건... 그냥 파일 하나만 보고 되었으면 좋겠다라는 소리죠. 생각해보니 좀 어처구니 없는 바램이기는 하죠..
  • NUL 2013/03/22 10:11 # 삭제 답글

    C++의 가독성은 참....
    C++은 보면 볼수록 놀랍고 신비롭고 재미나지만, 이걸 업무로 쓰는 건 정말 피곤한 일입니다.
    전체 프로그래머 중 남의 코드를 유지보수 하는 인원이 최소한 절반은 훌쩍 넘는다는걸 생각하면 더더욱 피곤한 일이구요

    굳이 C++을 쓰지않아도 되는 분야에서 C++ 사용을 강요할땐 난감 합니다.
    보통 그런걸 강요하는 결정권자는 C == C++ == MS VS로 생각하죠...
    더러 프로그래머라고 불리우는 사람까지 그런 생각을 가진 분들 만나면 멘붕옵니다.

    그리고 전 이거 저거 다 합쳐져서 언어 자체가 너무 복잡한게 제일 짜증 납니다.
    빌드 시간이 긴거도 그렇고, 소스 파싱하는 IDE가 리소스를 왕창 먹어 버버벅 거리는 것도 그렇고,
    C++의 유명세가 사그라 들어서 제발 필요한 분야에서만 C++를 쓰는 문화가 정착되었으면 좋겠어요
  • 김민장 2013/03/23 04:14 #

    C++ != C 이죠. 자세히 보면 다른게 참 많습니다. sizeof('a')가 C++과 C에서는 다르게 나오는 것부터 해서...
  • wafe 2013/03/22 10:43 # 삭제 답글

    자바나 씨샵도 별 차이 없다는 식으로 보면 진짜 별 차이 없어 보이기도 합니다. 언어자체가 좀 복잡한 부분을 없애는 식으로 디자인 되었다고는 해도 결국 테스팅이나 최적화를 위해서 소스를 고치지 않고 시스템 구성을 변경할 수 있는 동적 특성을 제공해야 하는 건 변함 없는 것 같습니다. 그래서 의존성 주입과 각종 디자인 패턴을 조합하고 그런 조합 관계를 외부 설정으로 빼내는 식으로 많이 하죠. 그러다보면 소스만 보고 파악 안되는 점은 동일하다고 볼 수 있죠. 새롭게 나온 환경들이다보니 기능이 적어서 깔끔해보이거나 좀 잘 만들어서 깔끔해보이거나... 이놈이나 저놈이나 똑같다는 식으로 보려고 하면 뭐 그렇다는 얘깁니다 ㅎㅎ
  • 김민장 2013/03/23 04:14 #

    복잡함을 줄이는 것은 어려운 것 같더라고요. 겨우 감출 수는 있지만 깊이 들어가면 결국 복잡성을 다 만나게 되는 것 같군요.
  • 아라크넹 2013/03/22 10:54 # 삭제 답글

    제가 최근 많이 보고 있는 Rust의 경우 많은 점에서 C++의 문제를 고치려고 노력한 흔적이 보입니다.

    - 다른 소스 파일을 파일명이 아닌 모듈명으로 접근합니다.
    - 모듈이 다른 모듈에 영향을 끼치는 정도가 제한됩니다. (Rust에는 문법 확장을 위해서 매크로 기능이 있지만 이건 Scheme 같이 위생적인 매크로인데다 매크로 호출 문법이 함수 호출과 별개라서...)
    - 링크 옵션을 소스 코드 안에 넣을 수 있습니다. (#pragma comment(lib, ...)와 동일)
    - 선택적 컴파일은 컴파일러에게 처음에 준 옵션으로만 제한됩니다. (#[cfg(linux)] 등)

    아직 몇 가지 아쉬운 것도 있지만요...

    - 아직 Makefile을 쓰기는 해야 합니다. 다만 object file은 없기 때문에 (Rust에서 컴파일의 단위는 crate라고 부르는 여러 모듈들의 집합입니다) 컴파일 옵션은 뻔하긴 합니다. 아마도 나중에 추가되지 않을까 싶네요...
    - generated code에 대한 문제는 C/C++랑 비슷하게 남습니다. 근데 이거 해결하는 언어가 존재하긴 했던가?...
  • 김민장 2013/03/22 15:26 #

    저도 Go나 Rust 같은 새로운 언어 좀 써봤으면 합니다. 나이가 먹어서 이제 취미로 만질 힘은 없군요. 정적 컴파일 언어의 빌딩 과정은 어떤 언어가 됐던 Makefile이 있는한...
  • Lohengrin 2013/03/22 11:13 # 답글

    전 도구보다는 사용하는 사람이 잘 써야 한다는 주의라서
    이런건 역시 사용하는 사람들이 Coding Convention을 잘 정의해서 써야 하지 않을까 싶네요.
  • 김민장 2013/03/22 15:27 #

    C++ 같이 방대한 언어는 코딩 컨벤션이 아주 중요하죠. 예를 들어, 레퍼런스는 쓰지 않는다.. 다중상속 쓰지 않는다.. 다만 이런 코딩규칙을 잘 보조해주는 툴은 있으면 좋겠습니다.
  • 미칸아빠 2013/10/25 21:33 #

    큰 c++프로젝트는 툴로 코딩규칙을 강제합니다.
    예를 들어 구글 c++코드 컨벤션은 다음과 같은데 http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
    이걸 어기면 커밋이 되지 않습니다. python으로 모든 항목을 검사하지요.

    c++이 여러 방언을 쓸수있어 쓰지 말자고 깐다면, javascript는 가루가 되도록 까여야 된다고 생각합니다.
    하지만 rust는 좀 멋져보여요. golang을 별루.. 저는 oop빠라..
  • 뽀도르 2013/03/22 13:34 # 답글

    modula-2 가 깔끔하고 좋아서 한동안 썼었는데, 남들은 아무도 안 쓰더군요-_-;; c/c++이 짜증나지만 사용자도 많고, 유산도 많아서 앞으로도 면면히 써나가지 않을지...
  • 김민장 2013/03/23 04:15 #

    교과서에서 이름만 들어봤던 모듈라..
  • 뉴 제타 2013/03/23 01:02 # 삭제 답글

    현역 C 프로그래머로서도 C는 사람 뒷골잡게 하는 게 정말 많죠.
    auto 커맨드는 B에서 넘어온 게 아직까지도 있는 거고(volatile이 잡혀있지 않는 변수 정의엔 자동적으로 들어갔다 봐도 됩니다)
    extern 스코핑은 더욱 웃겨요.

    int x = 1;
    int foo() {
    int x = 0;
    {
    // extern int x;
    return x;
    }
    }
    이 코드는 0을 리턴시키는데, 코멘트대로 extern int를 해두면 1을 리턴시키는 경우도 있습니다.

    반 어셈블리라서 이런 것도 가능합니다. int x = 'FOO!'; 외따옴표에요. 'F'는 1바이트의 char 니까, 4개를 붙이면 4바이트 값이 나오기에 가능합니다. 컴파일러도 불평을 안 하죠.


    그런 반면에 legacy긴 하다만 아직까지 C가 아니면 해결할 수 없는 것도 있어요. restrict 커맨드 등으로 쓰는 pointer aliasing 해결 등이라던지 말이죠.


    p.s. 혹시 학생분이 계시면 친구 헤더파일에 이렇게 한번 적어보세요. 즐거운 추억을 보장합니다.
    #define struct union
  • 김민장 2013/03/23 04:13 #

    Some dark corners of C 에 나오는 내용이네요 ㅎㅎ 추가적인 정적 분석 도구로 제한된 C/C++ 사용을 할 수 있도록 하는 것이 가장 좋은 방법이라고 봅니다.
  • 뉴 제타 2013/03/23 06:55 # 삭제

    #define struct union은 학창시절에 실제로 해본적이 있던지라 보고 한참 웃었죠.
댓글 입력 영역