1. 경고와 에러
C/C++ 컴파일러가 소스 코드를 컴파일 하는 중에 내뱉을 수 있는 메시지는 흔히 에러 또는 경고라는 두 종류로 나뉜다. 그런데 이걸 더 세분화하면 에러의 앞에는 수위가 더 높은 ‘심각한 에러’(fatal error)라는 게 있다.
얘는 컴파일 중에 컴파일러 자체가 뻗거나 메모리가 부족할 때처럼, 외부 요인에 의해 컴파일이 더 진행될 수 없을 때 나는 편이다. 그런 게 아니면 소스 코드가 문법상으로는 이상이 없지만 각종 수식이나 명칭 선언이 괄호가 너무 깊게 들어가고 복잡할 때, 리터럴 데이터 같은 게 너무 많아서 도저히 감당이 안 될 때, #include 깊이나 #define 치환 단계가 너무 깊을 때..
한 마디로 컴파일러의 한계 때문에 코드 생성이 안 되는 것이 심각한 에러로 분류되는 편이다. 이건 통상적인 컴파일 에러와는 성격이 다르기 때문이다. 참, #include 파일을 아예 찾을 수 없는 것, 그리고 #error로 대놓고 에러를 발생시킨 것 역시 추가적으로 심각한 에러의 범주에 든다.
일례로, int a = 999999999999999999999999; 이런 거야 상수가 너무 커서(32비트 범위 초과) 토큰의 스캐닝 단계에서 튕겼기 때문에 일반 컴파일 에러이다.
하지만 int tbl[] = { 10,45,34,33, ... }; 다음에 숫자가 한 100만 개쯤 있다거나,
char msg[] = "......" \ 이런 리터럴이 100MB쯤 이어져서 컴파일이 실패하는 것은 심각한 에러가 되는 셈이다.
그리고 괄호들이 닫히지 않은 채로 구문을 종료하는 세미콜론이 나오면 일반 에러이지만.. 그 상태로 파일 내용이 끝나 버리면 보통 심각한 에러로 간주된다.
에러 말고 경고는.. 컴파일러들이 경고를 이미 여러 단계로 분류해 놓은 편이다. 가령, 초기화되지 않은 변수를 사용하는 것은 다소 심각한 수위의 경고이지만.. 선언만 해 놓고 사용하지 않은 변수는 상대적으로 덜 심각한 경고이다.
또한 요즘은 정적 분석기가 함수 인자의 annotation까지 참조해서 미주알고주알 지적해 주는 잠재적 오류 가능성도 경고의 연장선이라고 볼 수 있다. "null 포인터를 참조할 가능성이 있다, 버퍼 오버런이 발생할 수도 있다" 따위 말이다.
요즘 세상에 코딩을 글쓰기에다 비유하자면 컴파일· 빌드는 인쇄· 배포에 대응하고, 정적 분석은 맞춤법 검사기와 비슷해 보인다.
2. 빌드 툴들이 말귀를 도무지 못 알아들을 때 확인해 볼 사항
- 그 소스 파일이 프로젝트에 포함돼 있긴 한가? 포함돼 있더라도 혹시 exclude from build 이런 낚시 옵션에 걸려 있지 않은가?
- 문제의 구간이 #if 조건을 만족하는 구간에 속해 있는가?
- 명칭이 이상한 매크로 때문에 다른 엉뚱한 형태로 치환되고 있지는 않은가? (주로 C)
- C++의 경우, 복잡한 namespace나 using 으로 인한 문맥 차이가 존재하지 않는가?
- 링크 에러의 경우, extern "C"로 인한 name mangling 방식 차이가 존재하지 않는가?
3. 빌드 속도
특별히 다른 부하가 걸린 게 없는 멀쩡한 개발자용 평균 사양의 2~3GHz급 컴터에서.. 2000줄 이하의 평범한 복잡도의 C++ 소스 파일이.. (IOCCC 입상작급 기괴한 난독화, 다단계 템플릿, namespace, #define 떡칠, 다중 다단계 상속 등의 남발.. 이 아닌 "평범한". -_-)
그것도 네트웍도 아닌 로컬 환경에서, 더구나 딱히 빡세게 최적화를 걸지도 않은 디버그 빌드가 컴파일하는 데 개당 0.8초 이상씩 걸리는 건.. 본인은 납득하기 어려운 상황이라고 간주한다. 소스 코드의 #include 구조 및 빌드 시스템에 문제가 있을 가능성이 높다.
당장 precompiled header가 제대로 적용돼 있는지, 덩치 큰 라이브러리 헤더의 연쇄 인클루드와 파싱이 무식하게 매번 반복되고 있지 않은지부터 확인해야 한다. 저건 컴터한테나 인간에게나 좋지 않은 상황이다.
DB 테이블로 치면 primary key 지정이나 인덱싱과 비슷한 최적화가 필요하다.
4. 안드로이드 앱용 JNI 라이브러리의 빌드
(1) 안드로이드 앱을 개발할 때, 겉에서 돌아가는 java 내지 kotlin 코드 기반의 프로그램이야 로컬 환경에서 Android Studio로 간편하게 빌드할 수 있다. 이 IDE는 Windows용과 mac용이 모두 깔끔하게 존재한다.
하지만 이 앱이 내부적으로 사용하는 native code 라이브러리들의 빌드 환경은 내 경험상 mac이건 리눅스건 여전히 유닉스 기반 터미널에 의존하고 있다. Windows에서 바로는 안 된다는 게 특이한 점이다.;; JNI 쪽 빌드도 IDE와 연계해서 같이 되게 할 수는 없는지..
(2) 디버깅도 앱은 breakpoint와 step in/out/over, 지역변수 값 확인, call stack 같은 통상적인 방법론이 IDE 차원에서 모두 지원되는 반면, 그 아래의 라이브러리는 그렇게 할 수 없다. 그런 내부 동작은 로그 printf 신공에 의존해서 추적하는 수밖에 없으니 몹시 불편하다.
(3) 그래도 이런 라이브러리들은 빌드 시스템이 멀티코어/멀티스레드 환경과 굉장히 잘 연계하는 편이다. 그래서 고성능 빌드 서버에서 make -j8 , -j16 이런 식으로 코어 수를 늘려 주면 빌드 속도가 정말 눈에 띄게 매우 빨라진다.
그런데 이 시설에도 이 기능에도 매우 아쉬운 점이 있는데... 코어 수가 늘어나면 빌드 에러 메시지도 진짜 정신없게 중구난방으로 튀어나와서 확인이 어려워진다는 것이다.
Visual Studio처럼 메시지의 앞에 코어 내지 프로젝트의 번호라도 좀 찍어 주면 읽기가 좀 더 수월할 텐데 말이다.
그리고 터미널 접속 프로그램의 본좌인 putty에는 특정 단어나 문자열이 등장했을 때 highlight를 시켜 주는 간단한 기능이 좀 있었으면 좋겠다.
putty는 20년이 넘게 0.x대의 버전 번호를 고수하고 있고, 유니코드(W)가 아닌 ANSI API를 사용하는 게 이색적이다.
ANSI API, 0.x 버전, 크로스 플랫폼 공개 소프트웨어라는 점에서는 DOSBOX하고도 무척 비슷하다.
5. 구조체 전방 선언의 부작용(?)
C/C++ 코드에서는 모듈 간의 include 의존도(= coupling)을 낮추기 위해서 자신이 내부적으로 취급하는 구조체는 불완전하게 전방 선언만 명칭만 노출하는 경우가 많다. 외부에서는 전방 선언 구조체의 포인터만 핸들 마냥 갖고 있고, 실제 조작은 실제 내부 구조를 아는 함수의 호출을 통해서를 하는 것이다. 뭐, 이게 C++이 말하는 정보 은닉과도 일맥상통하는 개념이며, 충분히 바람직한 디자인 패턴이다.
하지만 디버깅을 하는 상황이라면 어떨까..??
조작하는 함수로 들어가기 전에, 즉 밖에서 breakpoint를 걸었다. 이때도 이 포인터가 가리키는 구조체 내용을 좀 조회할 수 있었으면 좋겠는데 그게 Visual Studio IDE에서 안 돼서 답답했던 경우가 많다. 그렇다고 구차하게 소스 코드를 고쳐서 디버깅일 때에 한해서 감춰 놓은 내부 구조체 몸체 선언 include를 시키고 싶지도 않다.
특정 상황에 한해서 컴파일 때는 참고하지 않는 다른 소스 코드의 디버깅 정보를 가져오는 기능이 있으면 좋을 것 같다.
6. 나머지
(1) 컴파일러와 링커는 오늘날까지도 환경 변수라는 게 쓰이는 얼마 안 되는 분야이기도 하다. 환경 변수라는 게 명령줄에서 실행 파일을 자동으로 찾는 PATH, 그리고 컴파일러가 사용하는 기본 include 및 라이브러리 디렉터리... 이것 말고는 쓰이는 곳이 정말 드물지 않은가? 자체적인 환경 설정 파일 같은 게 동원될 법도 한데 컴파일러와 링커는 GUI 프로그램이 아니다 보니 좀 더 저수준이면서 실행되는 세션별로 사용자가 값을 더 간단하게 변경할 수도 있는 환경 변수를 대신 선택한 것 같다.
(2) 과거에 도스용 Turbo C/C++ 같은 물건은 굳이 프로젝트 파일을 안 만들어도 소스 하나만 단독으로 달랑 열어서는 곧장 빌드해서 돌려 볼 수 있었다. 그러나 요즘 개발툴들은 단순 텍스트 에디터 이상의 매우 복잡하고 방대한 물건이기 때문에 그렇게 할 수 없다. Hello world! 한 줄짜리 프로그램을 만들더라도 최소한의 프로젝트 세팅은 한 뒤에야 빌드와 디버깅이 가능하다.
(3) 그리고 요즘 개발툴들은 여러 소스 파일들을 한데 묶은 프로젝트로도 모자라서.. 프로젝트도 여러 개를 한데 묶은 '솔루션, workspace'라는 개념으로 운용된다는 것이 주지의 사실이다. 이 정도는 돼야 좀 규모 있는 소프트웨어를 원활히 개발 가능하기 때문이다.
(4) 컴터 프로그램 개발을 하다 보면.. 디버깅 로그가 실시간으로 뜨게 해 놓은 채로 디버기 프로그램을 구동하고 일정 주기로 결과를 확인할 때가 있다.
그런데 이때 프로그램이 출력하는 로그만 넣는 게 아니라, 사용자가 로그에다가 인위로 "=======" 같은 가로줄 같은 걸 즉석에서 추가할 수 있으면 좋겠다는 생각이 든다. 한 프로그램에서 동작 시험을 여러 번 할 때 로그의 영역을 하기 위해서이다.
(5) 앞으로는 "주 메모리에 로드되어 실행된 프로그램 / 하드디스크에 설치돼 있는 프로그램 / 원본 설치 패키지"라는 소프트웨어의 통상적인 3단계 구분이 더 모호해지고 단순화되지 않을까 생각된다.
일단 웹 프로그램은 설치라는 과정이 없는 게 확실하며, 메모리 계층에서 보조 기억장치와 주 메모리의 구분이 모호해지는 것도 이런 추세를 더욱 부채질할 테니 말이다.
Posted by 사무엘