You are on page 1of 5

TECHNICAL FEATURE

Beginner Corner

초보자를위한
C 언어프로그램스타일가이드 IV
지난 호에는 우수한 프로그램을 작성하는데 있어 가장 중요한 이식성(Portability)에 대해서 알아보았다. 이번 호는
본 연재기사의 마지막으로 그 외 상수, 조건부 컴파일, 고려사항 등에 대해 살펴보도록 하자.

편역: 박찬일/(주)하솜정보기술 대표이사


pak@hasom.com

환문에서 어레이에 인덱스를 주는 경우가 이에 해당된다.


다음과 같은 코드 작성은 매우 합리적이다.
본 자료는“Recommended C style and Coding
Standards”
를 편역한 것으로, 이는 AT&T사의 벨연구소에서
발표된 논문인“Indian Hill C style and Coding Standards”
for ( i = 0; i < ARYBOUND; i++ )
를 기초로 하여 알기 쉽도록 풀어서 설명했다.

반면 다음과 같은 코드는 사용하지 않는 것이 좋다.


상수(constants)

숫자상수는 직접 코드화해서는 안된다. C 언어의 전처리기의 door_t �front_door = open(door[i],7);


#define 기능을 사용하여 상수에 의미 있는 이름을 부여해야 if(front_door == 0)
한다. 기호적 상수들은 코드를 더 읽기 쉽게 해준다. 어느 한 곳 error(“can’t open %s\n”,door[i]);
에서 값을 정의하므로 수정도 한 곳에서만 하면 전부 고쳐져 큰
프로그램을 다루는데 용이하다. 선형적인 값을 갖고 있는 변수
는 enum 데이터 타입으로 하는 것이 추가적인 타입을 체크할 위 예제 문장에서 front_door는 포인터 변수이다. 포인터의
수 있어 좋다. 적어도 직접 표기된 숫자 상수는 그 값의 유도를 경우는 0과 비교하기 보다는 NULL과 비교해야 한다. NULL
설명하는 주석을 가져야 한다. 은 표준 I/O 라이브러리로 제공되며, 이는 stdio.h나 stdlib.h
따라서 상수는 그것들의 사용과 일관되게 정의되어야 한다. 파일에 정의되어 있다. 심지어 간단한 0이나 1과 같은 값이라고
예를 들면 float 값에 대해서는 묵시적으로 값이 형변환(cast) 할지라도 TRUE나 FALSE와 같이 표기하는 것이 좋다(YES나
되는 540 보다는 540.0을 사용하는 것이 좋다. 숫자상수 1과 0 NO를 사용하는 것이 나을 때도 있다).
은 경우에 따라서 #define을 사용하지 않을 때가 있다. for 순 단일 문자 상수는 숫자라기보다 문자로 정의되어야 한다. 특

112
초보자를 위한 C 언어 프로그램 스타일 가이드 IV

히 스트링 안에서 텍스트 코드가 아닌 글자의 사용이 필요하다


if ( x == 3 )
면 3개의 8진수 숫자로써 표기해야 한다(예:‘\007’
).
SP3( );
else
BORK( );
매크로(macro)

복잡한 수식은 매크로의 변수를 이용함으로써 해결할 수 있


다. 모든 매크로 매개변수가 괄호로 묶여 있지 않으면 연산자 위 문장에서 만약 SP3를 부른 후에 세미콜론이 생략되었다
우선순위에 있어 문제가 발생할 수가 있다. 이렇게 함으로써 매 면 위 문장의 else 부분은 자연스럽게 SP3 매크로에 있는 if 문
크로의 수식문장에서 부작용(side effect)을 피하는 것을 제외 장과 연결이 된다. 또한 위 문장에서와 같이 세미콜론을 넣은
하고는 변수와 관련된 부작용이 일어날 수 있는 문제는 거의 없 경우에도 문제가 발생할 수 있는데, 이는 else 부분이 어떤 if
다. 그리고 매크로를 만들 때는 가능한 변수를 한번 사용하도록 문장과도 연결이 되지 않는다는 점이다. 그러므로 SP3 매크로
해야 한다. 그러나 매크로를 만들 때 함수와 같이 동작하도록 는 안전하게 다음과 같이 작성되어야 한다.
만드는 것이 불가능한 경우가 있다.
몇몇 매크로는 함수처럼 사용되거나(예: getc, fgetc), 함수
#define SP3( ) \
구현시 사용되어 매크로의 변화가 함수에 자동적으로 반영된
do{ if(b) { int x; av=f(&x); bv += x; }} while (0)
다. 함수는 변수값에 의해 전달되고(by value), 매크로의 변수
자체가 대체되기 때문에 함수와 매크로를 서로 바꿀 때에는 주
의가 필요하다. 따라서 매크로를 문제없이 잘 활용하기 위해서 do-while 문장이 다소 어색하고, 간혹 어떤 컴파일러는
는 심사숙고해 작성해야 한다. while 조건문 안에 상수가 있다고 경고를 주기도 한다. 선언문
매크로 작성시 전역변수(global)가 매크로 안에 숨겨지므로, 을 위하여 매크로를 사용하는 것은 프로그램 작성을 용이하게
매크로에서 사용해서는 안된다. 매크로가 변수의 값을 바꾸거 해준다.
나 할당문의 왼쪽 부분(left value) 역할로 사용된다면 그것에
대한 주석을 달아야 한다. 매크로가 매개변수가 없으면서 변수
를 사용할 경우(기존의 지역, 전역변수)나 문장이 긴 경우, 그리 #ifdef lint

고 다른 함수를 부르기만 할 경우(aliasing) 매개변수를 사용하 static int ZERO;

지 말아야 한다. #else

#define ZERO 0

#endif
#define OFF_A( ) (a_global+OFFSET) #define STMT( stuff ) do{ stuff } while (ZERO)
#define BORK( ) (zork( ))
#define SP3( ) if(b) {int x;av=f(&x);bv += x; }

SP3를 다시 작성해 본다.


매크로를 사용하면 Call/Return시 시간을 줄일 수 있으나
(주: 함수는 매개변수 및 지역변수를 스택에 넣고 사용한다), 만
#define SP3( ) \
약 매크로의 크기가 커진다면 Call/Return시 시간은 문제가 되
STMT ( if(b) { int x; av=f(&x); bv += x; } )
지 않으므로 이런 경우에는 함수를 사용해야 한다. 경우에 따라
서는 컴파일러가 확실히 동작하도록 매크로를 세미콜론으로 끝
내는 경우도 있다. 형변환(cast)이나 sizeof, 위와 같은 hacking의 경우를 제외

Embedded World 113


TECHNICAL FEATURE

하고 예약어(keywords)를 사용할 때에는 전체 매크로를 브레


#ifdef BSD4
이스로 묶어야 한다.
long t = time ((long �)NULL);
#endif

조건부 컴파일(conditional compilation)

조건부 컴파일은 기계종속성, 디버깅(debugging), 그리고 컴 위 프로그램에서는 다음과 같은 두 가지 점에서 좋지가 않다.
파일시에 어떤 옵션들을 설정하는데 유용하게 사용된다. 그러 첫 번째는 BSD4 시스템에서 위와 같은 기능을 수행하는 더욱
나 조건부 컴파일은 사용시 주의해야 한다. 전혀 예기치 못한 좋은 코드가 있을 수도 있으며, 반면에 BSD4가 아닌 시스템에
기능들을 쉽게 결합시킬 수 있기 때문이다. #ifdef를 사용하여 서 위와 같은 코드가 가장 좋은 코드일 수도 있다는 점이다. 두
기계종속성을 해결할 경우, 만약 타깃 디바이스를 정해주지 않 번째는 #ifdef 문장에서 심벌(symbol)‘t’대신에 TIME_
았다면, 이 결과는 오류(error) 상황이지 기본상황(default)으 LONG이나 TIME_STRUCT 등으로 사용해야 한다는 점이다.
로 선택된 것이 아니라는 것을 주의해야 한다. 그리고 이들은 config.h와 같은 상황설정에 관련된 파일에 위
또한 #ifdef을 사용하여 선택적으로 최적화(optimize)할 경 치시키는 것이 좋다.
우, 기본상황으로 컴파일할 때는 최적화할 수 없는 코드이지 컴
파일할 수 없는 코드가 아님을 주의해야 한다. 즉 최적화할 수
ANSI C
없는 코드인지 아닌지 항상 확인해야 한다.
#ifdef 부분이 거짓(false)이라고 할지라도 어떤 컴파일러는 많은 C 컴파일러는 ANSI C 표준을 지원한다. 가능하다면 표
#ifdef 문장을 처리(scanned)하기도 한다. 그러므로 #ifdef 문 준 C에 따라 프로그램하는 것이 좋다. 그리고 함수선언, 상수,
장이 절대 컴파일 되지 않는다고 해도 임의의 문장을 장황하게 volatile 등의 기능을 이용해야 한다. 표준 C는 최적화하기 쉽
써서는 안된다. 도록 많은 소스들이 있어 속도를 증진시킬 수 있다. 표준 C는
#ifdef 문장은 가능하면 프로그램 파일에 위치시키지 말고 헤 모든 컴파일러에서 사용될 수 있도록 하고, 기계종속성을 피할
더파일에 위치시켜야 한다. 프로그램에서 일관성 있게 사용되 수 있는 기능을 제공함으로써 이식성을 증가시키고 있다.
는 매크로를 정의하기 위해서는 #ifdef를 사용해야 한다. 예를
들면 메모리 할당을 검증(checking)하기 위한 헤더파일은 다음 호환성(compatibility)
과 같다. 프로그램 작성시 컴파일러에 이식하기 쉽도록 한다. 예를 들
어, global.h 파일에 const나 volatile에 대해‘#define new’
예약어를 사용하여 조건적으로 컴파일할 수 있도록 한다. 표준
#ifdef DEBUG

extern void �mm_malloc( );


컴파일러는 미리 정의된 전처리기 심벌(pre-processor

#define MALLOC(size) (mm_malloc(size))


symbol) __STDC__이 있다. 컴파일러에서 void는 사용가능하

#else
나 void �를 사용하지 못하는 경우가 많으므로 void �를 사용

extern void �malloc( );


하는데 주의해야 한다.

#define MALLOC(size) (malloc(size))


컴파일러에서 사용하는 char �를 사용하기위해 VOIDP를

#endif
정의해 사용하는 것이 쉽다(컴파일러에서는 void �대신에

char �를 사용한다).

조건부 컴파일은 상황에 따라 맞게 행해져야 하며, 일반적으


#ifdef __STDC__
로 디바이스나 운영체제에 따른 종속성에 문제가 되는 것을 피
typedef void �voidp;
해야 한다.

114
초보자를 위한 C 언어 프로그램 스타일 가이드 IV

함수에 전달하지만, 만약 함수선언이 없다면(nonproto typed)


#define COMPILER_SELECTED
#endif
“c”
는 항상 integer 타입으로 전달한다. 위와 같은 문제는 프로
#ifdef A_TARGET 그램을 작성할 때 매개변수 선언을 함으로써 해결할 수 있다.
#define const 예를 들어 다음과 같은 문장에서 위의 예와 같이 함수선언이
#define volatile 되었다면 제대로 동작한다.
#define void int
typedef char �voidp;
#define COMPILER_SELECTED void

#endif bork(char c)

#ifdef ... {

... ...

#endif
#ifdef COMPILER_SELECTED
#undef COMPILER_SELECTED 위와 같은 문장은 ANSI C가 아닌 경우에 문제가 발생하므
#else
로, 다음과 같은 방법을 이용하여 ANSI와 ANSI가 아닌 C 언어
{ NO TARGET SELECTED! }
에서 공용으로 사용할 수 있도록 한다.
#endif

#ifdef __STDC__
ANIS C에서는 전처리 명령어를 나타내는 # 심벌이 줄의 처 #define PROTO(x) x
음에 놓이나, C에서는 줄의 처음 글자가 # 심벌로 시작되면 안 #else
된다. #define PROTO(x) ( )

#endif
형선언(Prototypes) extern char �ncopies PROTO((char �s, short times));
함수의 선언(function prototype)은 코드를 더욱 완벽하게
하고 처리속도를 증가시키기 위해서 꼭 사용해야 한다.
다음과 같은 함수선언은 위 문장에서 PROTO는 이중괄호로 되어있음을 주의해야
한다.

extern void bork ( char c);

주요 고려사항(special consideration)

다음과 같은 문장과 함께 동작하지 못한다. 지금부터 몇 가지 해야 할 것과 하지 않아야 할 것을 언급


한다.
void �매크로를 사용해서 C 언어의 문법을 바꾸지 말 것. 왜냐하
bork ( c ) 면 프로그램을 작성한 사람을 제외하고는 해당 프로그램의
char c; 인식이 불가능해지기 때문이다.
{
�불연속적인 값(예: 정수)을 필요로 할 때는 부동소수점 변
....
수를 사용하지 말 것. 순환문에 부동소수점 값을 사용하면
실행속도가 상당히 느려지기 때문이다.
위 문장에서“c”
는 기계에 가장 적합한 타입인 byte 크기로 �컴파일러는 버그가 있다. 흔히 발생하는 문제는 구조문 할

Embedded World 115


TECHNICAL FEATURE

당과 비트 field와 관련된 부분에서 발생한다. 일반적으로 LINT


어떤 컴파일러가 어떤 버그를 갖고 있다고 단정해서 말할 Lint는 프로그램 파일에서 비호환적인 타입 및 함수 정의와
수는 없다. 함수 사용에 있어 일관성의 결여 및 프로그램상의 버그를 찾아
단지 모든 컴파일러에서 문제가 있는 것으로 알려진 구조 내는 프로그램‘checker’
이다. 모든 프로그램에서 프로젝트의
적인 것을 피할 수 있는 프로그램을 만드는 것이 가능할 뿐 공식적인 검증방법으로 LINT를 사용하는 것이 좋다. LINT를
이다. 버그를 만나서 컴파일러가 작업중간에 곤경에 빠진 사용하는 최선의 방법은 프로그램의 공식적인 검증 전에 극복
다면 유용한 어떤 것도 만들 수 없다. 되어야하는 장벽으로서가 아니라 코드의 수정이나 추가시에 유
�beautifer(프로그램을 블록별로 묶어서 보기 좋도록 공간 용하게 사용되는 도구로서 인식되어야 한다.
을 띠워주는 도구)를 믿어서는 안된다. 좋은 프로그램 스타 다음의 예는 fprintf에서 변수를 생략한 버그 문장으로 LINT
일로부터 얻는 장점은 바로 프로그래머 자신이며, 특히 프 는 이와 같은 버그를 미리 찾아내준다.
로그램 작성 전에 만든 알고리즘이나‘Psedo Code’
임을
알아야한다.
beautifer는 단지 구조적으로 완전한 프로그램들에만 적용 fprintf(“Usage: foo -bar <file>\n”);

될 수 있다. 프로그래머 자신이 주의 깊고 신중하게 시각적


으로 보기 좋은 프로그램을 작성하는 것이 좋다. 일반적인
프로그래머는 beautifier에 의존하기 보다는 주의 깊게 자 또 하나의 유용한 도구는 make이다. 개발도중에 make는
신이 작성해나가는 프로그래머가 되도록 노력해야 한다. 마지막 make가 사용된 후로부터 변화하는 모듈만을 재컴파일
�논리적 비교에 있어서 자주 일어나는 실수는‘==’
에서 두 한다.
번째‘=’
를 생략하는 것 때문에 문제가 많이 발생하므로 조
심하여야 한다. 조건을 판단하는 문장에서 묵시적으로 처
결론
리하는 것은 피해야 한다.
여러 표준들이 C 프로그래밍 스타일을 위해 제시되어졌으며,
이중 가장 중요한 것들로는 다음과 같은 것들이 있다.
abool = bbool;

if(abool) { ...
�코드의 설계로부터 프로그램의 구조가 명확히 드러나도록
공백과 주석을 적절히 사용한다. 가능하면 이해가 쉽도록
단순한 문장과 함수를 사용해야 한다.
내재된 할당문(embedded assignment)을 사용할 때는 test �당신이나 다른 사람이 코드를 변형시킬 것을 요청받거나
문을 명백하게 나타내줌으로써 후에 다시 수정하는 문제를 피 다른 기계에서 실행시켜야 될지도 모른다는 것을 염두에
할 수 있다. 두어야한다.
�많은 스타일 중에 선택은 임의적이다. 일관된 스타일을 갖
while((abool = bbool) != FALSE) { ... 는다는 것이 절대적인 규칙에 따르는 것 보다 중요하다. 혼
while(abool = bbool) { ... /� VALUSED�/ 합된 스타일이 하나의 나쁜 스타일 사용보다 훨씬 못하다.
while(abool = bbool,abool) { ... �어떤 표준을 갖고 있다하더라도, 그것이 유용하다면 따라
야한다. 만약 이 표준들을 따르는데 문제가 있다고 할지라
도 단순히 그것을 무시해서는 안된다.
�최근 컴파일러는 변수들을 레지스터에 자동적으로 할당한
다. 자주 사용되는 변수는 레지스터 변수로 선언해 주는 것
이 좋다.

116

You might also like