minzkn의 코딩규칙
- 작성자
- 고친과정
2005년 7월 22일 : 처음씀
2010년 10월 21일 : 규칙개정
1.1. C/C++
- 규칙의 관점 (본 규칙을 작성하는데 기준이 되는 사항)
- 헝가리안 표기법같이 대소문자 혼합표기규칙은 Shift키의 접근이 매우 빈번하다는 점에서 불편하다고 생각되어 대문자는 되도록 사용하지 않도록 정의
- 헝가리안 표기법같이 변수명에 타입이 명시되는 규칙은 어느정도 도움이 되기는 하지만 그것보다는 재진입성 여부를 판별하도록 하여 경쟁조건등에 보다 신경쓰는 방향으로 선택하도록 정의
- 코드의 호환성을 최대한 높이기 위해서 다양한 문법의 허용보다는 제한적이지만 과거부터 호환되어온 표준을 좀더 비중있게 사용하도록 정의
- 편집기마다 달라질수 있는 View의 다양성을 고려하여 모든 View에서 동일한 모양이 나오기 위한 사항을 정의
- C언어의 간결함의 특징을 활용하기 보다는 재사용성이 높은 코드를 생성하기 위한 심볼규칙 정의
- 들여쓰기
- TAB은 되도록이면 들여쓰기에 사용하지 않는다. (소스편집기 및 사용자마다 선호하는 TAB size가 다르기 때문)
- 한번의 들여쓰기는 4칸의 Space(0x20)로 사용한다.
- 주석
- 해당 소스가 C++에서만 사용할것이 아니라면 C++의 주석인 "//"는 절대로 사용하지 않는다. (ISO C99에서는 이것이 C에서도 정식으로 채택되었으나 ISO C89이하 표준을 지키는것이 컴파일러 호환성에서 좋다고 생각됨)
- C의 주석인 "/* ... */" 만을 사용
- 내포된 주석형태 "/* ... /* ...?... */ ... */"와 같은 형태의 주석은 사용을 금지한다.
- typedef의 선언
- typedef에 의해서 선언된 심볼은 모두 앞에는 "__"를 붙이고 뒤에는 "_t"를 붙인다.
- typedef에 의해서 선언된 심볼은 반드시 앞에 "__"를 붙이지 않은 define을 함께 정의한다.
- 즉, 다음과 같은 형태로 선언을 한다.
#if !defined(my_int_t) typedef int __my_int_t; # define my_int_t __myint_t #endif
- 이렇게 typedef와 define이 함께 사용되어 선언되면 다음과 같이 코드상에서 해당 type이 선언되어 있는 경우와 아닌 경우 구현을 달리할수 있기 때문이다.
#if defined(my_int_t) my_int_t value; #else signed int value; #endif
- 공용체 및 구조체 선언
- 되도록이면 멤버변수는 앞에 "m_"를 붙인다.
- typedef 선언과 함께 선언한다.
#if !defined(my_interface_t) typedef struct my_interface_ts { int m_member_value; }__my_interface_t; # define my_interface_t __my_interface_t #endif
- Library의 Header로 배포되어 사용되는 경우 반드시 구조체 Align을 지정한다. 이것은 Library빌드환경과 배포환경에서의 빌드환경에서 컴파일러의 Align기본값이 다른 경우 발생하는 문제의 소지를 막을수 있다.
#if !defined(my_interface_t) # pragma pack(push,8) typedef struct my_interface_ts { int m_member_value; }__my_interface_t; # define my_interface_t __my_interface_t # pragma pack(pop) #endif
- 재진입이 불가능한 변수의 선언
- 전역변수와 같이 재진입이 불가능한 변수는 Global의 의미를 갖는 "g_" 를 prefix로 사용한다.
int g_argc; int main(int_s_argc, char **s_argv) { g_argc = s_argc; }
- 전역변수와 같이 재진입이 불가능한 변수는 Global의 의미를 갖는 "g_" 를 prefix로 사용한다.
- 재진입이 가능한 변수의 선언
- 지역변수와 같이 재진입이 가능한 변수는 Stack의 의미를 갖는 "s_" 를 prefix로 사용한다.
int main(void) { int s_exit_code = EXIT_SUCCESS; return(s_exit_code); }
- 만약 static 으로 선언하였는제 이를 변경하지 않는것이 확실하고 함수내에서 지역적으로 이용되는 경우는 다음과 같이 표기할수 있다.
int main(void) { static char sg_welcome[] = {"Hello world"}; (void)fprintf(stdout, "%s", (char *)(&sg_welcome[0])); return(EXIT_SUCCESS); }
- 지역변수와 같이 재진입이 가능한 변수는 Stack의 의미를 갖는 "s_" 를 prefix로 사용한다.
- 값의 변경이 허용되지 않는 변수의 선언
- const 형과 같이 1회만 초기화가 가능한 변수는 Constant value의 의미를 갖는 "c_" 를 prefix로 사용한다.
int main(void) { const int c_exit_code = EXIT_SUCCESS; static char cg_welcome[] = {"Hello world"}; (void)fprintf(stdout, "%s", (const char *)(&cg_welcome[0])); return(c_exit_code); }
- const 형과 같이 1회만 초기화가 가능한 변수는 Constant value의 의미를 갖는 "c_" 를 prefix로 사용한다.
- 외부참조를 원하지 않는 심볼의 선언
- extern 선언과 같이 외부 참조를 허용하는 경우가 아닌 static 전역심볼선언의 경우 앞에 "__"를 붙여서 이를 명확히 의사를 표현한다.
#define __def_default_private_key (0x12345678) static int __g_private_key = __def_default_private_key;
- extern 선언과 같이 외부 참조를 허용하는 경우가 아닌 static 전역심볼선언의 경우 앞에 "__"를 붙여서 이를 명확히 의사를 표현한다.
- goto label의 선언
- 성능상 유리하고 코드가 간결한 경우에만 goto 구문을 사용하는것으로 제한한다.
- label은 앞에 "_l"(소문자 L) 를 붙이며 들여쓰기는 하지 않는다.
int io_control(int s_ioport) { if(s_ioport == (-1)) { goto l_return; } /* ... */ l_return:; return(0); }
- Macro의 선언
- 모든 매크로는 "def_" 를 prefix로 사용한다. 그리고 괄호로 감쌀수 있는 값의 경우는 항상 관호로 감싸준다. 특히 수식의 경우는 반드시 괄호로 둘러싸서 연산자 우선순위에 따른 문제를 봉쇄한다.
#define def_default_buffer_size (32 << 10)
- 모든 매크로는 "def_" 를 prefix로 사용한다. 그리고 괄호로 감쌀수 있는 값의 경우는 항상 관호로 감싸준다. 특히 수식의 경우는 반드시 괄호로 둘러싸서 연산자 우선순위에 따른 문제를 봉쇄한다.
- 함수명의 결정
- 모든 함수는 name space를 신중히 고려하여 결정하며 해당 모듈명을 반드시 prefix로 사용한다.
/* 모듈명이 "mzapi"라고 한다면 */ void mzapi_hello_world(void) { (void)fprintf(stdout, "Hello world\n"); }
- 반드시 name space 를 고려하여 변수명을 결정해야 한다. get_byte, TRUE, FALSE, ERROR, debug 같은 함수명이나 매크로명을 절대로 사용하지 않는것을 원칙으로 해야 하는데 이러한 변수명의 사용은 재사용성을 떨어뜨리는 결정적인 요인중에 하나이기 때문이다.
- 모든 함수는 name space를 신중히 고려하여 결정하며 해당 모듈명을 반드시 prefix로 사용한다.
- 완전한 형변환자의 사용
- 특별히 애매한 경우가 아니라면 명확한 Casting을 꼭 해준다.
int example_puts(const char *s_string) { return(fprintf(stdout, "%s", s_string)); } int main(void) { char const c_const_string[] = {"Hello world !\n"}; (void)example_puts((const char *)(&c_const_string[0])); return(0); }
- 특별히 애매한 경우가 아니라면 명확한 Casting을 꼭 해준다.
- 함수의 원형와 프로토타입을 명확히 분리하며 extern구문을 정확히 사용한다.
- 함수의 원형이 선언되면 반드시 프로토타입을 선언한다.
- 외부참조인 경우 extern 구문을 반드시 표시한다.
/* header.h */ extern int example_info(void); /* 함수의 외부참조 선언 */ /* source.h */ int example_info(void); /* 함수의 프로토타입 선언 */ int example_info(void) { /* ... */ return(0); }
- 대괄호 규칙
- 일반 함수는 open과 close가 같은 칸에 맞춘다.
void my_function(void) { /* open */ } /* close */
- 함수나 조건문등 함수를 제외한 나머지 모든 open과 close는 open이 해당 조건문의 뒤에 오고 close는 해당 조건문의 첫 칸에 맞춘다.
if (my_expression == 0) { /* open */ } /* close */ for(;;) { } do { }while(0); switch (case) { case: break; default: break; }
- 일반 함수는 open과 close가 같은 칸에 맞춘다.
- 대소문자의 사용
- 심볼에 되도록이면 대문자를 사용하지 않는다. (대소문자를 혼용하여 사용하므로써 얻어지는 이득보다 심볼예측성에서 혼란이 더 크다고 생각되므로 되도록이면 소문자만 사용한다.)
- 심볼에 되도록이면 대문자를 사용하지 않는다. (대소문자를 혼용하여 사용하므로써 얻어지는 이득보다 심볼예측성에서 혼란이 더 크다고 생각되므로 되도록이면 소문자만 사용한다.)
- 상수를 비-상수로 변환하고자 할때는 형변환 캐스팅으로 해결하지 않고 공용체를 통해서 해결한다.
/* 이 예제의 경우 함수내에서 포인터의 변형이 일어나지는 않지만 반환받는 곳에서는 const로 받지 않는 경우를 제시한것이다. */ char *never_null_string(const char *s_string, const char *s_default_string) { union { char *m_string; const char *m_const_string; }s_complex_string; if(s_string == ((const char *)0)) { s_complex_string.m_const_string = s_default_string; } else { s_complex_string.m_const_string = s_string; } return(s_complex_string.m_string); }
- 가급적 함수 인자의 개수는 5개 이하로 제한
- 일부 아키텍쳐의 경우 함수 인자를 제한된 개수의 Scratch register 를 통해서 전달할 수 있으며 이를 고려한다면 인자를 5개 이하로 함수 설계를 하도록 한다.
1.2. Assembly
- 들여쓰기
- TAB은 되도록이면 들여쓰기에 사용하지 않는다. (소스편집기 및 사용자마다 선호하는 TAB size가 다르기 때문)
- 한번의 들여쓰기는 4칸의 Space(0x20)로 사용한다.
- 제어구문은 들여쓰기를 되도록이면 하지 않고 OPCODE에 해당하는 명령들만 한번의 들여쓰기를 한다.
- 대소문자
- 되도록이면 소문자로 통일한다.