메뉴 건너뛰기


Developer > Application

C,C++ Linux 커널 코딩 스타일(Coding Style)

2013.11.27 02:36

푸우 조회 수:20111

리눅스 커널 코딩 스타일

이 문서는 리눅스 커널에서 주로 사용되는 코딩 스타일을 설명한다. 코딩 스타일은 매우 개인적이므로 나의 견해를 다른 사람에게 강요하고 싶진않다. 하지만 이것은 내가 성취하기 위해서 유지해야만 하는 것들이며 나는 다른 어떤 것보다도 이 스타일을 좋아한다. 최소한 여기서 지적하는 부분에 대해서 한번씩 생각해 보길 바란다.
우선, 난 GNU 코딩 표준 문서를 한 부 출력하기를 제안하고 싶다. 그리곤 읽지않고 태워버리자. 그건 매우 상징적인 제스쳐가 될 것이다.
하여튼, 한번 시작해 보도록 하자.

챕터 1: 인덴트

탭은 8 글자, 마찬가지로 인덴트도 8글자로 한다. 인덴트를 4(또는 2)글자의 깊이로 바꾸려는 이단자들의 움직임이 있다. 그것은 파이(PI)값을 3으로 선언하는 것과 마찬가지의 행동이다.
근거: 인덴트 뒤에 숨어있는 전체적인 아이디어는 블록 컨트롤이 어디서 시작하고 끝나는지 명확하게 선언하는 것이다. 특히 당신이 모니터를 20시간동안 뚤어져라 쳐다봐야 하는 상황에서는 그 점을 좀 더 쉽게 발견할 수 있다. 만약 넓은 인덴트를 사용했다면 그것이 어떻게 동작하는지 쉽게 볼 수 있기 때문이다.
지금, 몇몇 사람들은 8글자 인덴트를 가지고 있는 것은 코드를 오른쪽으로 너무 멀리 이동시키며, 그것은 80 글자 터미널 스크린에서 더욱 읽기 힘들다고 주장한다. 거기에 대한 답변은 3 레벨 이상의 인덴트가 필요가 없다는 것이다. 만약 그들이 3레벨 이상의 인덴트를 필요로 한다면 그들의 코드는 어쨌든 꼬이는 것은 마찬가지이기 때문에 반드시 프로그램을 고쳐야 할 것이다.
짧게 말해서, 8글자 인덴트는 코드를 읽기 쉽게 만들어 주며 너무 깊게 함수를 중첩시키는 것을 경고해주는 추가적인 잇점이 있다. 그 경고를 무시해서는 안된다.

챕터 2: 괄호 위치

C 스타일과 관련되서 항상 튀어나오는 다른 이슈는 괄호를 어디다 위치시키냐 하는 것이다. 인덴트 사이즈와 다르게, 다른 것을 배제하고 한 가지 배치 전략을 선택하는 것은 아주 조금의 기술적인 이유밖에 없다. 그러나 좋아라 하는 방법은 열기 괄호는 라인 끝에 위치시키고, 닫기 괄호는 라인의 처음에 위치시키는 것이다. 이러한 방법은 커닝헌과 리치와 같은 선지자들이 우리에게 보여준 것이기도 하다. 아래와 같이 될 것 이다.
       if (x is true) { 
              we do y 
       } 
그러나, 거기에는 함수라는 한가지 특별한 경우가 있다. : 그것들은 다음과 같이 열기 괄호를 다음 줄의 시작에 위치시킨다.
       int function(int x) 
       { 
              body of function 
       } 
전세계에 분포한 이단자들은 이것은 모순이다... 음 불완전한다...,라는 주장을 펼친다. 그러나 바르게 생각하는 모든 사람은 (a) K&R은 올바르며 (b) K&R도 옳다는 것을 알고 있다. 게다가, 함수는 어쨌든 특별하다 (C에서 함수는 중첩이 불가능하다).
닫기 괄호는 그것 자체만 있는 줄로 비어 있어야 한다는 점을 알아둬라. 물론 연속적으로 같은 구문이 따라오는 경우는 제외한다. 예를들면 do 구문에 포함된 "while" 이나 if 구문에 포함된 "else"같은 것들이다. 그것들은 다음과 같이 쓴다.
       do { 
              body of do-loop 
       } while (condition); 
또는
       if (x == y) { 
              .. 
       } else if (x > y) { 
              ... 
       } else { 
              .... 
       } 
근거: K&R.
또한, 이렇게 괄호를 배치시키는 것이 가독성에 대한 어떠한 손해도 없이 비어있는(또는 거의 빈) 라인의 수를 최소화 한다는 것을 알아야 한다. 그렇게 함으로써, 낮은 해상도의 터미널에서도(80x25라인 터미널이 여기 있다고 생각해봐라), 코멘트를 달 수 있는 빈 라인들을 더 얻을 수 있을 것이다.

챕터 3: 네이밍

C는 엄격한 언어며, 네이밍에도 그러한 것이 적용된다. Modula-2나 Pascal 프로그래머들과 달리, C 프로그래머들은 ThisVariablesATemporaryCounter와 같은 귀여운 이름을 사용하지 않는다. C 프로그래머는 아마도 그 변수 이름을 "tmp"로 지을 것이다. 그러는 편이 훨씬 쓰기 쉽고, 그다지 이해하기 어렵지도 않기 때문이다.
그러나, 대소문자가 섞인 이름이 눈살을 찌푸리게 하지만 , 전역 변수는 반드시 설명적인 이름으로 지어야 한다. 전역 함수를 "foo"라고 부르는 것은 잘못된 것이다.
전역 변수는(실제로 그것들을 필요할때만 사용해야 한다) 전역 함수가 그랬듯이 설명적인 이름을 가질 필요가 있다. 활성화된 사용자 수를 세는 함수가 있다고 가정해 보자. 아마도 "count_active_users()"내지는 그와 비슷한 이름을 지을 것이다. 그걸 "cntusr()"이라고 불러서는 안된다.
함수 타입을 이름에 인코딩 하는 것은(소위 헝가리언 표기법이라 불린다) 완전히 쓸모없는 짓이다. - 컴파일러는 어쨌든 타입을 알고 있으며 그것들을 체크할 수 있다. 그리고 그것은 단지 프로그래머를 혼란시키기만 한다. 마이크로소프트가 버그 투성이의 프로그램을 만드는 것에 대한 의문의 여지가 없는 것이다(역주: MS의 수석 개발자 찰스 시모니가 헝가리언 표기법을 창안했다).
지역 변수 이름은 반드시 짧아야 한다는 것이 포인트다. 정수 루프 카운터에 사용되는 변수는 "i"로 불리는 것으로 족하다. 거기에 오해의 소지가없는한, 그것을 "loop_counter" 라고 부르는 것은 생산적이지 않다. 마찬가지로 "tmp"는 임시 값을 가지고 있는데 사용하는 어떤 타입의 변수도 될 수 있다.
만약 당신 지역 변수 이름을 섞어 쓸것 같아서 걱정된다면, 당신은 다른 문제도 가지고 있을 것이다. 그것은 함수-성장-호르몬-불균형 신드롬이라고 불리는 것이다. 다음 챕터를 보면 알게 될 것이다.

챕터 4: 함수

함수는 한가지 일만하는 간단하고 규칙적인 것이어야 한다. 그것들은 스크린 크기의 두배 정도의 텍스트에 들어가야하며(우리 모두가 알고 있듯이 ISO/ANSI 스크린 사이즈는 80x24다.) 한가지일을 처리하며 그것을 잘 해야한다.
함수의 최대 길이는 해당 함수의 인덴트 레벨과 복잡도에 역으로 비례한다. 따라서, 컨셉적으로 간단한 함수들에 대해서는 길게 작성하는 것도 괜찮다. 주로 그런것들은 많은 수의 작은 것들을 많은 다른 경우에 수행해야 하는때에 사용하는, 단지 길기만 한(그러나 간단한) case 구문으로 구성된것들이다.
그러나, 복잡한 함수라서 고삐리들 조차도 그 함수가 무엇을 하는 것인지 정확하게 이해하지 못한다고 생각한다면, 반드시 세밀하게 두페이지 분량의 제한을 지킬 필요가 있다. 그럴때에는 대신 설명적인 이름을 지닌 헬퍼 함수들을 사용하면 된다 (만약 그것이 성능과 매우 밀접한 연관이 있다면 inline함수를 사용할 수 있다. 그리고 그것이 네가 할 수 있는 행동중에 가장 괜찮은 것이다).
함수의 다른 측정 기준의 하나는 지역 변수의 갯수이다. 그것들은 5-10개를 넘지 않아야 한다. 그렇지 않으면 뭔가 잘못되고 있는 것이다. 함수에 대해서 다시 생각해 보고, 그리고 그것을 더 작은 조각으로 분리하도록 해야 한다. 사람의 뇌가 일반적으로 쉽게 기억할 수 있는 것은 7가지 다른 것들에 불과하다. 그것보다 많다면 혼란을 야기시킬 것이다. 네가 똑똑하다고 생각한다면 지금으로부터 2주전에 작업한 것이 무엇인지 이해해 보길 바란다.

챕터 5: 코멘트

코멘트는 좋다. 그러나 과도한 코멘트에는 또한 위험이 도사리고 있다. 절대로 어떻게 코드가 동작하는지를 코멘트로 설명하려고 시도하지 마라. 코드를 그것이 하는일이 분명해 지도록 쓰는것이 좋다, 그리고 잘못 씌여진 코드를 설명하는 것은 시간 낭비다.
일반적으로, 코멘트는 코드가 무엇을 하는지를 말해야한다. 어떻게 하는지를 말하는 것이 아니다. 또한 코멘트를 함수 내부에 두는 것을 피해야한다. 만약 함수가 너무 복잡해서 그것의 일부분을 분리해서 코멘트로 남겨야 할 필요가 있다면, 챕터 4로 돌아가 보길 바란다. 특별한 꽁수에(이상한것) 경고나 알림을 하는 작은 코멘트를 만들수는 있다. 그러나 과도한것은 피해야한다. 대신, 코멘트를 함수 앞에 두도록 한다. 사람들에게 그것이 무엇을 하는지, 왜 그것을 하는지 말해주는 것이다.

챕터 6: 모든걸 망쳐 버렸을 때

괜찮다. 우린 모든것을 할 수 있다. 오래된 유닉스 사용자들은 "GNU emacs"가 C 소스를 자동으로 포맷팅할 수 있다고 말한다. 맞는 말이다. emacs는 그걸 해준다. 그러나 디폴트 옵션을 사용한다면 우리가 원했던 것을 얻을 수 없을 것이다 (사실, 그것은 랜덤 타이핑 보다 못하다 - 무한대의 원숭이가 GNU emacs에서 타이핑 해봤자 좋은 프로그램이 만들어질리 없다).
이쯤되면 아마 GNU emacs를 사용하지 않는다는 사람도 있을 것이고 스캐너 옵션을 변경하려는 사람도 있을 것이다. 후자쪽을 선택했다면, .emacs 파일에 아래와 같은 줄을 추가하면 된다.
(defun linux-c-mode () 
  "C mode with adjusted defaults for use with the Linux kernel." 
  (interactive) 
  (c-mode) 
  (c-set-style "K&R") 
  (setq c-basic-offset 8)) 
이것은 M-x linux-c-mode 커맨드를 정의하는 것이다. 파일의 처음 시작 두줄에 -*-linux-c-*- 이 문장을 놓게되면 이 모드는 자동적으로 실행된다. 이것을 추가하고 싶을 것이다.
(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\.[ch]$" . linux-c-mode) 
                       auto-mode-alist)) 
위의 내용을 .emacs 파일에 추가하면, /usr/src/lunux 아래의 소스를 편집할때 자동으로 linux-c-mode가 켜지게 된다.
emacs를 사용해서 코드를 포맷팅하는데 실패했다면 좌절할 필요없다. "indent"를 사용하면 된다.
GNU indent는 GNU emacs처럼 쓸모없는 설정을 가지고 있다. 이것이 바로 몇줄의 커맨드라인 옵션을 주어야 하는 이유다. 그러나, 나쁘진 않다. 왜냐하면 GNU 인덴트도 K&R의 권위에 대해서는 인식하고 있기 때문이다 (GNU 사람들이 악마는 아니다. 그들은 단지 이 문제를 심하게 잘못 인도하고 있을 뿐이다), 따라서 인덴트 옵션으로 단지 "-kr -i8" ("K&R", 8 글자 인덴트를 의미한다)만을 주면 된다.
"indent"는 많은 수의 옵션을 가지고 있다. 특히 . 코멘트를 다시 포맷팅해야 할때에는 매뉴얼 페이지를 한번 쯤 읽어보는 것이 도움이 될 것이다. 그러난 기억해 두어야 할 것은 "indent"는 잘못된 프로그램을 고쳐주진 않는다는 점이다.

챕터 7: 환경 설정 파일

환경 설정 옵션들(arch/xxx/config.in과 모든 Config.in 파일들)을 위해서는 다른 인덴트가 사용된다.
코드에서는 인덴트 레벨 3이 사용되었다. 반면에 환경 옵션을 위한 텍스트에서는 의존 관계를 구분하기 위해서 인덴트 레벨 2를 사용한다. 후자의 경우는 단지 bool/tristate(삼상) 옵션에만 적용된다. 다른 옵션에는 단지 상식을 적용시키면 딘다. 예를 살펴보자.
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then 
   tristate 'Apply nitroglycerine inside the keyboard (DANGEROUS)' CONFIG_BOOM 
   if [ "$CONFIG_BOOM" != "n" ]; then 
      bool '  Output nice messages when you explode' CONFIG_CHEER 
   fi 
fi 
일반적으로, CONFIG_EXPERIMENTAL은 안정적이지 않다고 생각되는 모든 옵션을 둘러싸고 있어야 한다. 데이터를 파괴하는 것으로 알려진(파일 시스템 쓰기 기능 지원같은) 옵션들은 (DANGEROUS)로 표기되어야 한다. 다른 실험적인 옵션은 (EXPERIMENTAL)로 표기된다.

챕터 8: 자료 구조

다른 쓰레드에서 접근할 수 있는 자료 구조들은 반드시 레퍼런스 카운트를 가져야 한다. 커널내부에는 카비지 컬렉션은 존재하지 않는다 (그리고 커널 가비지 컬렉션은 느리고 비효율적이다). 그것은 사용하는 것마다 무조건 레퍼런스 카운트를 가져야 한다는 것을 의미한다.
레퍼런스 카운트는 락을 피할수 있고, 여러 사용자들이 자료 구조에 병렬적으로 접근하는 것을 허럭한다는 것을 의미한다. - 그리고 슬립이나 잠시동안 다른 일을 처리하느라 자료 구조들이 갑자기 사라지는 현상에 대해서 걱정할 필요는 없다.
락이 레퍼런스 카운트의 대안이 될 수 없음을 알아야 한다. 락은 자료 구조들이 일관성을 유지하기 위해서 사용한다. 반면에 레퍼런스 카운트는 메모리 관리 테크닉이다. 일반적으로 둘다 필요하고 그것들이 서로 혼란스럽지 않다.
많은 자료 구조들은 다른 "classes"의 유저들이 있을때, 두가지 단계의 레퍼런스 카운트를 가지고 있다. subclass 카운트는 subclass 사용자들의 갯수를 헤아린다. 그리고 전역 카운트는 subclass 카운트가 0이 되었을때 단지 한번만 감소 시킨다.
이러한 종류의 다중 레퍼런스 카운팅(multi-reference-counting)은 메모리 관리("struct mm_struct": mm_users와 mm_count)에서 발견된다. 또한 파일 시스템("struct super_block": s_count 와 s_active) 코드에서도 볼 수 있다.
분명하게 기억해야 할 것은 다른 쓰레드에서 참조하는 자료 구조에 레퍼런스 카운트가 없다면, 거의 대부분의 경우 거기에는 확실한 버그가 있다.