2011

logo_main.gif


글 : 정재준 rgbi3307@nate.com / 커널연구회 (www.kernel.bz ) 2011-02-08

연재 순서

 1. C언어 소개
 2. 형태, 연산자, 표현
 3. 제어 흐름
 4. 함수와 프로그램 구조
 5. 포인터와 배열
 6. 구조체
 7. 알고리즘 소개
 8. 소팅을 통한 알고리즘 분석
 9. 스택(Stack) 실습
10. 큐(Queue) 실습
11. 리스트(List) 실습
12. 트리(Tree) 실습
13. 해싱(Hash) 실습
14. 알고리즘 설계 및 분석기법
15. 진보된 알고리즘 소개

함 수는 대규모 프로그램 수행 임무들을 작은 단위로 나누는 역할을 하며 한번 사용한 것을 재사용할 수 있도록 해준다. 또한 함수 내부의 자세한 코드를 모두 확인하지 않아도 쉽게 사용할 수 있으므로 프로그램의 유지 보수성을 증대시켜준다. C언어는 함수를 효율적이면서도 사용하기 쉽도록 설계했다. C 프로그램은 일반적으로 작은 함수들이 여러개 모여서 구성된다. 또한 소스 파일들이 여러개로 분리되어 있으며 컴파일러가 이들을 각각 컴파일한다.  그런다음 로더에 의해 라이브러리에 이미 컴파일되어 있는 함수들과 함께 적재된다. 함수를 선언할때 매개변수의 형태를 선언하도록 한다. 이렇게 함으로써 함수가 실제적으로 정의될 때 매개변수 형태가 상호 일치하는지 점검하여 컴파일 오류를 줄일 수 있다.

함수의 기초

함 수는 다음과 같은 형식으로 구성된다. “return-type”은 함수가 반환하는 데이터 타입이다. 이것은 생략되면 정수형으로 간주된다. “function_name”은 사용자가 정의한 함수명이다. 괄호안에 있는 “argument declarations”은 함수 안으로 전달되는 파라미터들의 데이터 타입과 명칭이다. 함수의 몸체는 중간괄호 { }로 둘러싸여 있으며 이곳에 함수가 임무를 수행하기 위한 각종 데이터 타입과 문장들을 기술한다.

return-type  function_name (argument declarations)
{
    declarations and statements;
}

프 로그램은 변수들과 함수들을 정의한 집합으로 구성되어 있다. 함수들간의 소통은 매개변수(입력값)와 반환값(출력값)을 통하여 하거나 외부변수를 사용한다. 함수들은 순서에 상관없이 소스 파일 안의 어느 곳이나 나타날 수 있고, 소스 프로그램 파일은 여러개로 나눌 수 있다.
간단한 예제를 통하여 함수를 이해해 보도록 하자. 먼저, 함수를 사용하지 않고 main에 실행 문장들을 모두 기술한 것을 실습해 보자.

(실습 소스)



/*
author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
comments:   함수의기초
*/

#include <stdio.h>

main ()
{
    int a = 10, b = 20;

    printf(“a = %dn”, a);
    printf(“b = %dn”, b);
    printf(“a+b = %dn”, a + b);

    printf(“nPress any key to end...”);
    getchar();
}


(실행 결과)


$ cc -o fn01 ch0401_fn01.c  < 컴파일 및 링크
$ ./fn01  < 실행
a = 10
b = 20
a+b = 30

Press any key to end...
 


아래는 위의 예제를 함수로 기술한 것으로서 실행결과는 동일하다.

(실습 소스)


/*
    author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
    comments:    함수의 기초
*/

#include <stdio.h>

//함수선언
void fn_a (int a);
void fn_b (int b);
int fn_ab (int a, int b);

main ()
{
    int a = 10, b = 20;
    fn_a (a);
    fn_b (b);
    printf(“a+b = %dn”, fn_ab (a, b));

  printf(“nPress any key to end...”);
    getchar();
}

//함수정의
void fn_a (int a)
{
    printf(“a = %d₩n”, a);
}

//함수정의
void fn_b (int b)
{
    printf(“b = %d₩n”, b);
}

//함수정의
int fn_ab (int a, int b)
{
    int c;
    c = a + b;
    return c;
}


(실행 결과)

$ cc -o fn02 ch0401_fn02.c  < 컴파일 및 링크
$ ./fn02  < 실행
a = 10
b = 20
a+b = 30

Press any key to end...
 


정수형이 아닌 것을 반환하는 함수들

앞 부분에서 함수가 반환하는 데이터 타입중에서 반환 데이터가 없는 void형과 int(정수)형에 대해서 경험해 보았으나, 이제는 다양한 데이터 타입에 대해서 살펴보도록 하자. 수학적인 계산을 위한 많은 함수들(sqrt, sin, cos..)은 대부분 double형의 데이터를 반환한다. 물론 다른 목적으로 특성화된 함수들은 해당 목적에 맞는 데이터를 반환하도록 한다. 예를 들어 문자열을 실수로 변환하는 atof()함수를 이해해 보도록 하자.

atof()함수는 파라미터로 입력받은 문자열 s을 double형의 실수로 변환하는 것으로, 앞에서 이미 실습해본 atoi()함수의 확장형이다.  atof()함수는 부호와 소숫점, 정수부와 소수부를 처리한다. 이것은 표준 라이브러리 함수로서 <stdlib.h> 헤더파일에 선언되어 있다.

   #include <ctype.h>

   double atof(char s[])
   {
    double val, power;
    int i, sign;

    for (i = 0; isspace(s[i]); i++)  //화이트 스페이스 무시
    ;
    sign = (s[i] == ‘-’) ? -1 : 1;
    if (s[i] == ‘+’ || s[i] == ‘-’)
    i++;
    for (val = 0.0; isdigit(s[i]); i++)
    val = 10.0 * val + (s[i] - ‘0’);
    if (s[i] == ‘.’)
    i++;
    for (power = 1.0; isdigit(s[i]); i++) {
    val = 10.0 * val + (s[i] - ‘0’);
    power *= 10;
    }
    return sign * val / power;
   }

참고적으로, atof 함수가 반환하는 double형을 int형으로 형변환(cast)하면 atoi 함수와 동일한 결과를 얻을 수 있는데, 이것을 활용하여 atoi 함수를 다시 작성하면 아래와 같다.

   //atof 함수를 활용하여 문자열을 정수형으로 변환
   int atoi(char s[])
   {
    double atof(char s[]);  //atof 함수 선언

    return (int) atof(s);  //정수형으로 형변환
   }

외부 변수들

C 프로그램은 외부 변수들이나 함수들의 집합으로 구성되어 있다. 외부(external)는 내부(internal)라는 의미와 반대되는 개념이다. 외부는 함수 바깥쪽, 내부는 함수 안쪽을 의미한다. 우리가 지금까지 사용해온 변수들은 함수 안쪽에 정의한 내부 변수들이었으며 함수에 전달되는 파라미터 또한 내부 변수이다. 외부 변수들은 함수 바깥쪽에 정의 되므로 소스 파일내의 모든 함수에서 사용할 수 있는 반면에, 내부 변수들은 함수 안에서만 사용할 수 있다. 함수 자신은 항상 외부이다. C언어는 함수를 정의할 때 또 다른 함수의 내부에는 정의하지 못한다.

외부 변수는 하나의 소스 파일 내에서 전역적으로 접근할 수 있으므로 함수들 사이에서 데이터를 교환하기 위한 매개변수나 반환 값들을 대신할 수 있다. 외부 변수가 정의된 하단의 모든 함수에서 외부 변수에 접근할 수 있다. 함수들간에 많은 수의 변수들을 공유해야 한다면, 길다란 매개변수 리스트를 사용하는 것보다 외부 변수들을 사용하는 것이 좀더 편하고 효율적일 수 있다. 그러나 외부 변수들을 너무 남용하게 되면 함수들 각각의 독립성이 부족해지므로 전체적인 프로그램 구조에 나쁜 영향을 미칠 수 있다.

영역(scope) 규칙들

C 프로그램을 이루는 함수들과 외부변수들은 동시에 컴파일 되지 않아도 된다. 프로그램 소스들은 여러 개의 파일들에 나누어져 있을 수 있고, 이미 컴파일 된 루틴들은 라이브러리에 저장될 수도 있다. 영역(scope)은 사용자가 정의한 명칭이 프로그램에서 사용되는 범위(수명)를 의미한다. 함수 안의 시작 부분에서 선언된 자동 변수의 영역은 그 함수 내부이다.

내부 변수는 그것이 정의된 각각의 함수안에서 통용되므로 각각의 함수마다 동일한 이름의 변수가 있어도 문제되지 않는다. 함수로 전달되는 파라미터들도 내부 변수와 동일한 의미로 취급된다. 외부변수와 함수는 파일내부에서 그들이 선언된 위치에서부터 파일의 끝까지 통용된다. 프로그램 소스 파일이 두 개 이상으로 나누어져 있을 때, 소스 파일간에 외부변수를 동일하게 취급하려면 extern 이라고 선언해 주어야지 동일한 기억장소를 사용한다.

예를 들면,

  in file1.c:
    extern int sp;
    extern double val;

  in file2.c:
    int sp = 0;
    double val;

위와 같이 file1.c와 file2.c에서 sp와 val을 동일한 외부변수로 사용하려면 file1.c에 extern으로 선언해 주어야 한다.

헤더 파일들

우 리는 이미 앞에서 #include <stdio.h> 헤더파일을 보았지만, 이것에 대한 자세한 개념을 지금부터 알아보도록 하자. 헤더파일은 다른 파일에 정의되어 있는 함수를 사용하고자 할 때 그 함수의 선언부를 컴파일러에게 알려주는 역할을 한다. 변수를 사용할 때 미리 변수의 데이터 타입을 선언하고 사용할 수 있는 것처럼 함수 또한 미리 선언해 주어야 하는데, 이때 다른 파일에 정의되어 있는 함수는 헤더파일에 선언부를 기술하고 그 함수를 사용하고자 하는 파일에서 해당 헤더파일을 #include로 포함시켜 주어야 한다.

헤더파일은 프로그램 소스를 여러 개의 파일로 나누어서 각각 분리된 형태로 컴파일 할 때 사용한다. 또한, 이미 컴파일 된 파일(라이브러리)에 있는 함수를 사용하고자 할 때도 사용한다.  규모가 큰 프로그램을 작성할 때 소스를 여러 개의 파일로 나누어서 작성하는 것이 필요하며, 이때 사용하는 다양한 함수들의 선언부를 공통된 헤더파일 안에 모아서 작성해 두는 것이 효율적이다.

고정(Static) 변수들

외 부 고정(Static) 선언은 함수들을 선언할 때 자주 사용한다. 함수명들은 전역적이기 때문에 프로그램 전체에서 접근 가능하다. 그러나, 함수가 고정(Static)으로 선언되면 이 함수가 선언된 파일내에서만 접근가능하고 다른 파일에서는 접근 불가능하다.

고 정(static) 선언은 내부 변수들에도 적용할 수 있다. 내부 변수는 함수가 실행될 때 할당 되었다가 함수 실행이 종료되면 할당 해제 되지만, 내부 변수를 고정으로 선언하면 함수의 활동 여부에 상관없이 내부 고정 변수가 선언된 함수의 내부에 지속적으로 남아있게 된다. 즉, 내부 고정 변수는 해당 함수 내부에서 자신만의 저장공간을 지속적으로 가지고 있는다.

레지스터 변수들

레 지스터(register) 선언은 변수를 CPU의 레지스터에 위치하도록 컴파일러에게 지시하는 역할을 한다. 변수가 CPU의 레지스터에 위치하면 처리속도가 빨라진다. 단, 레지스터(register) 선언은 컴파일러에 의해서 선택적으로 받아들여 진다. 레지스터(register) 선언은 다음과 같다.

   register int  x;
   register char c;

레지스터 변수는 아래처럼 자동 변수나 함수의 파라미터 변수에 선언할 수 있다.

   function (register unsigned m, register long n)
   {
    register int i;
    ...
   }

실 제적으로 레지스터 변수들은 제약사항들이 있다. 레지스터는 용량이 제한되어 있기 때문에 단지 몇 개의 변수들만 레지스터에 배치될 수 있다. 레지스터 선언이 수량 제한을 초과하면 컴파일러에 의해서 무시되기 때문에 문제는 발생하지 않는다. 레지스터는 주소가 없기 때문에 주소(포인터)를 통해서는 접근할 수 없다. 기타 레지스터 변수의 특징은 머신(CPU)의 종류에 의존한다.

블록 구조

C 언어는 함수 내부에서 또 다른 함수를 정의하지 못하지만, 변수들은 함수 내부에서 블록 구조로 정의할 수 있다.  C언어에서 블록은 중간괄호 {으로 시작해서 }으로 끝난다. 블록 안에 선언된 변수는 블록이 끝나는 곳에서 소멸된다. 아래 코드에서 정수형 변수 i는 if 조건이 참인 블록 안에서 유효하다. i는 자동변수이며 블록에 진입한 상단에서 초기화되고 블록에서 벗어날 때 소멸된다.

   if (n > 0) {
    int i;  //i변수 선언

    for (i = 0; i < n; i++)
    ...
   }
함 수로 전달되는 매개변수도 자동변수이다. 아래 코드에서 함수 f()로 전달되는 double x는 함수 f()의 블록 내부에서만 유효하고 함수 f()의 블록을 벗어나면 소멸 되므로 상단에서 전역변수로 선언된 int x와 변수명은 같지만 다른 데이터 타입인 double로 취급된다. 즉, 변수 x는 함수 f()의 블록 외부에서는 정수형으로 함수 f()의 블록 내부에서는 double형으로 취급된다.

   int x;
   int y;

   f(double x)
   {
    double y;
   }

그러나 위의 코드와 같이 변수명 x을 블록 외부와 내부에서 동일한 명칭으로 사용하는 것은 코드를 유지 보수할 때 혼동의 문제가 있으므로 피하는 것이 좋다.

(실습 소스)
/*
    author:    Jung,JaeJoon (rgbi3307@nate.com ,http://www.kernel.bz/ )
   comments:    블록구조
*/

#include <stdio.h>

main()
{
    int n = 10;
  int i = 20;

    if (n > 0) {
    int i;  //새로운변수i 선언
    for (i = 0; i < n; i++)
    printf (“%d “, i);
    printf (“₩ni=%d₩n”, i);
} else {
    int i = 30;
}
    printf (“i=%d₩n”, i);

    printf(“₩nPress any key to end...”);
    getchar();
}


(실행 결과)


$ cc -o ch0408ch0408_block01.c   < 컴파일 및 링크
$ ./ch0408   < 실행
0 1 2 3 4 5 6 7 8 9
i=10
i=20

Press any key to end...
 


(실습 소스)


/*
author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
comments:   블록구조
*/

#include <stdio.h>

static int x, y;

fn (double x)
{
    double y;

    y = x;
    printf (“y=%f₩n”, y);
}

main ()
{
    x = 10;
    y = 20;

    fn (2.71828);

    printf(“₩nPress any key to end...”);
   getchar();
}


(실행 결과)


$ cc -o ch0408 ch0408_block02.c   < 컴파일 및 링크
$ ./ch0408   < 실행
y=2.718280

Press any key to end...
 


초기화

변 수의 초기화에 대해서 지금까지 여러 번 언급되었으나, 몇 가지 중요한 사항을 알아보도록 하자. 변수를 선언할 때 대입(=) 연산자를 사용하여 초기화를 명시적으로 하지 않으면, 외부(external) 변수와 고정(static) 변수는 영으로 초기화 되고, 자동(automatic) 변수와 레지스터(register) 변수는 초기화 되는 값 없이 임의의 값(garbage)이 들어간다. 변수의 초기화는 아래와 같이 대입(=) 연산자를 사용하여 명시적으로 할 수 있다.

   int x = 1;
   char squota = “₩’”;
   long day = 1000L * 60L * 60L * 24L; /* milliseconds/day */

외 부와 고정 변수는 초기화 되는 값이 상수 값으로 표현 되어야 하며 프로그램의 실행이 시작될 때 한번만 초기화가 이루어 진다. 반면에 자동변수와 레지스터 변수는 초기화되는 값이 상수가 아니어도 되며 다양한 표현식이 가능하다. 자동변수와 레지스터 변수는 사용자의 취향에 맞게 명시적으로 초기화시켜 주는 것이 좋다.

재귀(Recursion)

C 언어의 함수는 재귀(Recursion)적으로 호출할 수 있다. 함수 내부에서 해당 함수를 다시 호출할 수 있는 방식인데 예제를 통하여 익혀 보도록 하자.

(실습 소스)


/*
    author:    Jung,JaeJoon (rgbi3307@nate.com ,http://www.kernel.bz/ )
    comments:    재귀호출
*/

#include <stdio.h>

void printd (int n)
{
   if (n < 0) {
    putchar(‘-’);
    n = -n;
   }
   if (n / 10)
    printd(n / 10);
   putchar(n % 10 + ‘0’);
}

main ()
{
    printd (123);

    printf(“₩nPress any key to end...”);
    getchar();
}


(실행 결과)


$ cc -o recur01 ch0410_recur01.c  < 컴파일 및 링크
$ ./recur01  < 실행
123

Press any key to end...
 


아래의 실습예제는 재귀호출을 잘 활용하고 있는 quick sort 코드이다. 이 알고리즘은 1962년에 영국의 컴퓨터 과학자 C.A.R.Hoare에 의해서 개발되었다.

(실습 소스)


/*
author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
comments  재귀호출(quicksort, a sorting algorithm developed by C.A.R.Hoare in 1962)
*/

#include <stdio.h>
//swap: 배열요소v[i] 와v[j]의값을바꿈
void swap (int v[], int i, int j)
{
   int temp;

   temp = v[i];
   v[i] = v[j];
   v[j] = temp;
}

//qsort: 배열요소v[left]에서v[right] 사이의 값을 오름차순으로 정렬
void qsort(int v[], int left, int right)
{
   int i, last;
   void swap(int v[], int i, int j);

   if (left >= right)
    return;

   swap (v, left, (left + right)/2);

   last = left;
   for (i = left + 1; i <= right; i++)
    if (v[i] < v[left])
    swap (v, ++last, i);

   swap(v, left, last);

   qsort(v, left, last-1);
   qsort(v, last+1, right);
}

main ()
{
    int i;
int v[] = {2, 9, 4, 7, 6, 7, 5, 8, 3, 1};

qsort (v, 0, 9);

for (i = 0; i < 9; i++)
    printf(“%d, “, v[i]);
 printf(“%d”, v[i]);
}


(실행 결과)


$ cc -o recur02 ch0410_recur02.c  < 컴파일 및 링크
$ ./recur02  < 실행
1, 2, 3, 4, 5, 6, 7, 7, 8, 9
 


C언어 전처리기(Preprocessor)

C 언어는 컴파일 시 우선적으로 처리하는 전처리기를 제공한다.  대표적인 것으로 #include와 #define, #if가 있다.  #include는 컴파일 시 파일의 내용물을 포함 시키는 역할을 하며, #define은 문자열을 정의하여 치환하는 역할을 한다.  #if는 조건부 컴파일을 하도록 해준다.

파일 포함기(#include)
파일의 내용물을 포함 시키는 #include는 아래와 같이 사용한다.

   #include “filename”
혹은
   #include <filename>

즉, filename의 내용을 포함시킨다. 파일명을 “filename” 처럼 “”을 사용하면 현재 소스 프로그램이 있는 곳에서 filename을 찾아서 내용을 포함시킨다. 파일명을 <filename>처럼 <>을 사용하면 파일이 구현될때 정의된 곳(라이브러리)에서 파일을 찾아서 내용을 포함시킨다.  #include는 프로그램의 규모가 크질때 변수들과 함수들의 선언부를 헤더 파일로 다함께 묶어서 포함 시키고시 할 때 유용하게 사용된다. 이렇게 하면 프로그램 소스의 유지보수 및 관리가 쉬어진다.

매크로 치환(#define)
#define을 사용한 매크로 치환의 문법은 아래와 같다.

  #define name replacement text

토 큰 name은 replacement text로 치환(교체,replace ment)된다. replacement text는 라인의 끝까지 기술할 수 있고, 너무 길다면 백슬래시 문자(₩)를 끝에 표시하여 개행 한 후 계속 표시할 수 있다. name으로 치환된 문장은 컴파일이 되면서 정의가 시작되는 시점에서 소스파일의 긑까지 유효하다.

여러 가지 예제를 통하여 #define으로 매크로를 정의하는 방법을 익혀보자.

#define forever for (;;)

위의 문장은 forever라는 name으로 무한루프를 정의한다. forever는 for(;;)로 치환되어 무한루프와 같은 문장이 된다. 또한 아래와 같이 매개인수(arguments)들을 정의하는 것도 가능하다.

#define max(A, B) ((A) > (B) ? (A) : (B))

함수호출처럼 보일지 모르나, max(A, B)는 인라인 코드로 치환된다.  예를 들면,

x = max(p+q, r+s);

위의 문장은

x = ((p+q) > (r+s) ? (p+q) : (r+s));

으로 치환(교체)된다.
그러나 이것을 사용할 때 몇 가지 함정에 빠지지 않도록 주의한다.

max(i++, j++);   /* 잘못된 사용 */

증가연산이 두 번 발생한다.

#define square(x) x * x

위의 매크로를 square(z+1)와 같이 호출하면 원하지 않는 결과가 나올 수 있다.

위 와 같이 몇 가지 사항을 주의하면, 매크로는 아주 유용하다. <stdio.h>에 있는 코드들을 보면, getchar 와 putchar 는 매크로로 정의되어 있어서 실행(run-rime)시 함수 호츨의 부하(overhead)를 피할 수 있다. 매크로를 정의할 때 문자열을 매개인수로 넘겨야 하는 경우가 있을 수 있다. 이때 매개인수에 #문자를 넣으면 된다. 예를 들면,

#define dprint(expr) printf(#expr “ = %g₩n”, expr)

이렇게 매크로를 정의하고,

dprint(x/y);

를 호출하면, 아래와 같이 치환된다.

printf(“x/y” “ = &g₩n”, x/y);

문자열이 합쳐져서 결국 다음과 같이 된다.

printf(“x/y = &g₩n”, x/y);

전처리기 연산자 ##은 매크로가 확장(치환)될 때 매개인자들을 합치는데 사용한다. 예를 들면,

#define paste(front, back) front ## back

이렇게 정의하면, paste(name, 1)는 name1 토큰을 생성한다.

(실습 소스)
/*
author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
comments:   전처리기, 매크로치환
*/

#include <stdio.h>

#define max(A, B) ((A) > (B) ? (A) : (B))
#define square(x) x * x
#define dprint(expr) printf(#expr “ = %d₩n”, expr)
#define paste(front, back) front ## back

main ()
{
    int i, j, k;
    int ia=5, ib=10, iaib=0;

    i = 3;
    j = 7;
    k = max (i++, j++);
    printf(“i=%d, j=%d, k=%d₩n”, i, j, k);

    printf(“square=%d, %d, %d₩n”, square(ia), square(ia+1), square(ia+2));
dprint(max(ia, ib));
 printf(“%d”, paste(ia, ib));
printf(“₩nPress any key to end...”);
getchar();
}


(실행 결과)

$ cc -o ch0411 ch0411_mac01.c   < 컴파일 및 링크
$ ./ch0411   < 실행
i=4, j=9, k=8
square=25, 11, 17
max(ia, ib) = 10
0
Press any key to end...
 


조건부 컴파일(#if)
프 로그램 소스를 컴파일하는 동안에 #if를 통하여 문장들을 조건적으로 포함시킬 수 있다. #if는 상수 정수 표현식을 평가한다(sizeof, 형변환, enum 상수들은 포함되지 않음). 만일 #if 표현식이 zero가 아니라면, 하위의 라인이 #endif 혹은 #elif 혹은 #else를 만날 때까지 포함된다(#elif는 else-if와 유사함). 만약 name이 정의 되어 있다면, #if에서 defined(name)으로 표현되는 논리 결과값은 1이고, 그렇지 않은 경우는 0이다. 예를 들면, hdr.h 헤더파일의 내용을 한번만 포함시키고자 한다면, 아래와 같이 #if 와 #endif 사이에 내용을 작성한다.

#if !defined(HDR)
   #define HDR
    /* 헤더파일 hdr.h 의 내용을 여기에 작성 */
#endif

hdr.h 헤더파일이 처음으로 포함(include)될 때 HDR이 정의된다. 그 이후에 포함되는 것들은 이미 HDR이 정의 되어 있어 #if의 조건을 만족하지 않으므로 #endif 다음으로 이동(skip)하게 된다. 위와 유사한 방식을 사용하여 특정 파일이 여러 개 포함되는 것을 방지하고, 특정환경에서 원하는 헤더 파일만 선택할 수 있다. #if 조건을 아래와 같이 구성하면 특정 SYSTEM에 맞는 헤더파일을 중복 없이 독립적으로 포함시킬 수 있다.

   #if SYSTEM == LINUX
    #define HDR “linux.h”
   #elif SYSTEM == BSD
    #define HDR “bsd.h”
   #elif SYSTEM == MSDOS
    #define HDR “msdos.h”
   #else
    #define HDR “default.h”
   #endif
   #include HDR

또한, #ifdef 와 #ifndef은 명칭(name)이 정의되어 있는지 여부를 판단한다. #if에 대한 위의 첫 번째 예제는 다음과 같이 다시 작성할 수 있다.

#ifndef HDR
   #define HDR
    /* 헤더파일 hdr.h 의 내용을 여기에 작성 */
#endif

(실습 소스)


/*
author:    Jung,JaeJoon (rgbi3307@nate.com , http://www.kernel.bz/ )
comments:    조건컴파일
*/

#include <stdio.h>
#define SYSTEM LINUX

#if SYSTEM == LINUX
   #define HDR “linux.h”
#elif SYSTEM == BSD
   #define HDR “bsd.h”
#elif SYSTEM == MSDOS
   #define HDR “msdos.h”
#else
   #define HDR “default.h”
#endif

//#include HDR

int main ()
{
    printf(“%s”, HDR);

    printf(“nPress any key to end...”);
    getchar();
    return 0;
}


(실행 결과)


$ cc -o ch0411 ch0411_mac02.c   < 컴파일 및 링크
$ ./ch0411   < 실행
linux.h
Press any key to end...
 



맺음말

이 번 연재에서는 C언어 함수의 기초, 정수형이 아닌 것을 반환하는 함수들, 외부 변수들, 영역(scope) 규칙들, 헤더 파일들, 고정(Static) 변수들, 레지스터 변수들, 블록 구조, 초기화, 재귀(Recursion), C언어 전처리기(Preprocessor) 등에 대해서 기술했다. 이러한 내용들은 C언어 프로그래밍을 구조적으로 하는데 도움이 되므로 많이 연습해 두도록 하자.

필 / 자 / 소 / 개

(정 재준) 필자는 학창시절 마이크로프로세서 제어 기술을 배웠고, 10여년동안 쌓아온 IT관련 개발 경험을 바탕으로 “오라클실무활용SQL튜닝(혜지원)” 책을 집필하고, “월간임베디드월드” 잡지에 다수의 글을 기고하였다.  서울대병원 전산실에서 데이터베이스 관련 일을 하면서 학창시절부터 꾸준히 해온 리눅스 연구도 계속하고 있다.  특히, 스탠포드대학교의 John L. Hennessy 교수의 저서 “Computer Organization and Design” 책을 읽고 깊은 감명을 받았으며, 컴퓨터구조와 자료구조 및 알고리즘 효율성 연구를 통한 기술서적 집필에 노력하고 있다.  또한, 온라인 상에서 커널연구회 (http://www.kernel.bz/ )라는 웹사이트를 운영하며 관련기술들을 공유하고 있다.


※ 본 내용은 (주)테크월드(http://www.embeddedworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
Copyright ⓒ
Techworld, Inc. 무단전재 및 재배포 금지

맨 위로
맨 위로