engineering/System Eng.

임베디드 프로그래밍 시대의 C 강좌 #2 - "포인터는 우스워"

theYoungman 2007. 2. 26. 10:41


1회 | C 복습! 컴파일, 그리고 소스 분리하기
2회 | 포인터는 우스워
3회 | 에러없는 프로그램에 도전한다
4회 | 소스 분석하기
5회 | 디버깅하기
6회 | C로 임베디드 시스템 맛보기
7회 | 임베디드 리눅스 애플리케이션 개발하기




포인터는 C 프로그래머가 사용할 수 있는 가장 강력한 무기입니다. 포인터가 없는 C는 C라고 할 수 없고 마찬가지로 포인터를 제대로 사용할 수 없는 C 프로그래머라면 C를 반만 사용할 줄 아는 것이라고 이야기해도 좋습니다. 또 C가 임베디드 시스템에 적합한 이유 중의 하나는 바로 이 포인터가 있어서라고 감히 이야기할 수 있습니다. 그만큼 포인터는 C에서 중요한 요소입니다.
C를 처음 학습하면서 포인터의 예제로 가장 많이 등장하는 함수는 아마도 swap()이라는 함수일 것입니다. 왜 포인터를 사용해야만 하는지에 대해서 그리고 함수에서 인자를 전달하는 방식을 설명하기 위해 가장 짧고 명확한 예제라고도 할 수 있겠지요.

void swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
하지만 이 swap이라는 예제는 여전히 포인터의 수많은 얼굴을 설명해 주기에는 부족합니다. 당장 필자의 오래 전 기억을 되살려 봐도 swap이라는 예제를 보고 포인터를 떠올리면서 대체 ‘&’는 왜 등장하는 것인지, int *a와 &a와 *a는 대체 어떻게 다른 것인지를 이해할 수 없었습니다(게다가 C도 제대로 모르는 주제에 배웠던 C++ 덕분에 &에 대한 혼란은 대단했었지요).
이번 호에서는 설명보다는 코드를 많이 적어두려고 합니다. 프로그래밍 학습은 백 마디의 말보다는 한 줄의 코드가 모든 것을 설명해준다는 것이 진리이지요. 예제 코드에는 가능한 많은 내용을 한 번에 담아두려 노력했습니다. 꼭 한 번씩 실행해보고 값을 바꾸며 실험하면서 미묘한 차이점을 직접 확인해 보기 바랍니다.


포인터는 대체 무엇일까
포인터는 단어 그대로 ‘가리키는 것’에 불과합니다. 한번 더 풀어서 이야기하면 ‘포인터는 메모리의 번지를 값으로 가지는 정수형 변수’입니다. 다시 한번 강조해 봅니다. 포인터는 32비트 시스템에서 32비트(= 4바이트)의 크기를 가지는 정수입니다. 물론 64비트 시스템에서라면 포인터는 당연히 64비트가 되는 것이 맞습니다. 왜일까요? 포인터의 값은 ‘메모리의 주소’이기 때문입니다. 이 시점에서 C의 변수형에 대해 복습도 할 겸 <리스트 1>의 코드를 한번 실행시켜 보세요.
<리스트 1>의 실행 결과를 보면 포인터는 모두 4바이트의 크기라는 사실이 증명되었습니다. 여기서 포인터의 속성을 조금 더 파악해보기 위해 <리스트 2>도 한번 실행시켜 보기 바랍니다. 물론 표시되는 주소값은 실행시키는 환경마다 다르겠지요. 현재 필자가 글을 쓰고 있는 환경에서는 이런 결과가 되었습니다.
<리스트 2>의 결과값을 제대로 이해할 수 있다면 독자는 기본적인 포인터의 개념을 확실히 알고 있는 것입니다. 이 실행 결과에서 pa+1의 값을 잘 살펴보기 바랍니다. 분명히 pa=2293484였는데, pa+1은 2293488이 되었습니다. 포인터를 이해하기 위해서는 이 점을 꼭 짚고 넘어가야 합니다. 왜 pa+1은 1이 아니라 4가 늘어난 것일까요?
포인터는 정수형 변수이지만 메모리의 위치를 값으로 가집니다. 그렇다면 대체 int *p와 long *p는 무엇이 다른 것일까요? 어차피 똑같은 정수값을 가진다면 값을 구분할 필요가 없을텐데…. 포인터의 변수형은 바로 포인터의 값을 증가시키는 시점에 의미를 가지게 됩니다. <리스트 2>의 예제에서 pa+1을 했을 때 pa는 정수형 포인터이므로 ‘메모리 상에서 다음 정수가 있어야 할 위치’를 가리키게 되는 것입니다. 물론 문자형 포인터였다면 1만큼만 증가하는 것이 맞겠지요. 그렇다면 만일 int a를 short a로 바꾸고 int *pa를 short *pa로 바꾸면 실행 결과는 어떻게 달라질까요? 직접 확인해 보세요.
포인터를 사용하기 위해서는 꼭 숙지하고 있어야 할 두 가지 기호가 있습니다. 바로 &와 *입니다. 아직 몰랐던 독자라면 꼭 기억하기 바랍니다. &는 주소, *는 주소가 가리키는 값입니다. 다시 말하면 a == *(&a)입니다. 포인터는 주소를 가리키는 값이므로 int *pa = a라고 하면 옳지 않은 연산이 됩니다. 반드시 int *pa = &a라고 해야 맞습니다.




배열과 포인터
결론부터 이야기하겠습니다. 배열과 포인터는 ‘같습니다’. 배열로 선언한 변수는 그대로 포인터로 사용해도 무방합니다. 공식을 적어볼까요. a = &a[0]이라고 암기하고 있어도 좋습니다. 배열과 포인터를 동일하게 취급할 수 있다는 이 사실은 실제로는 char의 배열인 문자열을 char의 포인터로 그대로 다룰 수 있다는 것과도 같은 말이 됩니다.

  문자열과 포인터
C에서의 문자열은 배열과 포인터의 개념이 없이는 이해할 수 없습니다. 물론 이 글을 읽고 있는 독자들은 C에서의 문자열이 ‘\0’으로 끝나는 문자의 배열이라는 사실 정도는 이미 알고 있으리라 생각합니다. 지금 이야기하려고 하는 내용은 문자열을 처리하는 함수를 작성하면서 포인터를 이용하면 배열을 이용하는 것보다 아주 간결하게 작성할 수 있는 경우가 많다는 것입니다. 문자열의 길이를 구하는 함수를 배열과 포인터의 두 가지 개념으로 작성해 보았습니다. 포인터를 이용해 strtok() 같은 함수를 구현한다면 포인터를 이용한 스트링의 처리에 대해 많은 것을 생각해 볼 수 있을 것입니다.



<리스트 3>에서 주의할 것은 문자열을 얻어내는 함수는 예제를 위해 아주 간단하게 구성한 것입니다. 실제로 제품이 될 코드를 작성하면서 이런 식의 ‘엄밀하지 못한 코드’를 작성하는 것은 자살행위(?)와도 같습니다. 꼭 명심하세요. 에러를 줄일 수 있는 엄밀한 코드의 작성에 관해서는 다음 호에서 다룰 예정입니다. 일단 궁금해 할 독자들을 위해 간단히 설명하면 앞의 함수에서는 포인터가 잘못된 값이 아닌지 미리 검사해줘야 합니다.


동적 메모리 할당
포인터와 동적 메모리의 할당은 정말 떼려고 해도 뗄 수가 없는 밀접한 관계에 있습니다. 포인터와 동적 메모리 할당이라는 것이 없으면 아마 프로그램을 작성하는 것이 불가능할지도 모르겠습니다. 하지만 C가 어려운 이유는 바로 이 부분 때문입니다. C는 지극히 간단한 언어이므로 결코 배울 내용이 많지 않은데도 C로 제대로 프로그램을 작성할 수 있는 프로그래머를 찾기 힘든 이유는 바로 포인터와 메모리 때문입니다. 훌륭한 C 프로그래머라면 바로 이곳에서 발생하는 문제들을 비교적 쉽게 해결하거나 예방할 수 있는 자신만의 노하우나 원칙을 틀림없이 가지고 있습니다.
초심자들이 포인터를 이용하면서 범하는 가장 큰 실수는 바로 배열에 메모리를 할당하거나 또는 포인터에 메모리를 할당하지 않고 사용하는 것입니다. 두 가지는 포인터와 ‘실제 사용하는 공간’을 혼동하기 때문에 생겨나는 일이라고 해도 좋습니다. 가장 큰 원칙은 다음과 같습니다.

① 포인터는 동적 할당, 또는 변수의 주소 할당을 통해 가리킬 공간을 지정해 주기 전에는 사용할 수 없다.
② 변수는 선언하는 것과 동시에 자동으로 공간이 할당된다. 물론 배열은 변수의 모음이므로 역시 공간이 이미 할당되어 있다.

다음 함수들을 살펴봅시다.

int wrong_malloc_to_array()
{
  int array[10];
  array = malloc(10);
}

이 함수는 문법조차 잘못된 함수입니다. 배열에 메모리를 할당할 수는 없습니다. 하지만 필자는 a = &a[0]라는 사실에 얽매여 왜 저런 식의 코드가 잘못된 것인지 한참 궁금해 했던 적이 있었습니다. 하지만 &a[0]은 값을 대입할 수 있는 변수가 아니므로 이 방법은 완전히 잘못된 것입니다.

int wrong_free_array()
{
  int array[10];
  free(array);
}

이런 코드를 과연 만들게 될까 생각하지만 의외로 malloc과 free에 대해 이해하지 못한 상태에서 이런 코드를 작성하는 경우를 필자는 무척 많이 보았습니다. 바로 앞의 예와는 달리 &array[0]에 값을 대입하는 코드가 아니므로 이 예제는 논리적으로는 완전히 잘못되었지만 문법적으로는 틀린 점이 없습니다.

int wrong_invalid_pointer()
{
  int *pa=NULL;
  *pa = 0;
}

다음은 포인터만을 할당해 둔 상태에서 가리킬 장소는 정해두지 않았지만 그곳에 값을 대입하려고 한 경우입니다. 실제로 이런 실수를 저지르는 경우는 아주 많습니다. 특히 코드가 복잡해지면서 이런 실수를 저지르는 경우는 무척 많습니다. 같은 예를 하나 더 들어보겠습니다.

int wrong_invalid_pointer()
{
  int *pa=NULL;
  pa = (int *)malloc(10);
  /* 코드 30줄 정도 */
  free(pa);
  /* 다시 코드 30줄 정도 */
  *pa = 0;
}


여기서 보인 것과 완전히 동일한 경우이지만 이런 실수는 의외로 찾아내기 힘이 드는 경우가 많습니다.


int wrong_no_free()
{
  int *pa = NULL;
  pa = (int *)malloc(10);
  *pa = 0;
  pa = (int *)malloc(5);
}

int wrong_two_free()
{
  int *pa = NULL;
  pa = (int *)malloc(10);
  free(pa);
  free(pa);
}


역시 잘못된 경우를 두 가지 보였습니다. 왜 잘못되었는지 찾는 것은 어렵지 않겠지요. 배열과 포인터를 설명하면서 배열과 포인터는 ‘같은 것’이라고 단정지었지만 이제는 다시 ‘다른 것’이라고 이야기를 할 때가 된 것 같습니다. 값을 사용하기 위해서는 배열과 포인터는 완전히 같은 방법으로 접근할 수 있습니다. 하지만 포인터에는 값을 대입할 수 있지만 배열에 포인터와 같은 방식으로 값을 대입하는 것은 잘못된 것입니다. 다음 예를 한번 살펴보기 바랍니다.


int wrong_string_assignment(const char *str)
{
  int array[10];
  int *pa;

  pa = str; /* Good */
  array = str; /* Bad : &array[0]에는 값을 대입할 수 없음 */

  strncpy(pa, str, 10); /* Bad : pa가 가리킬 공간이 할당되지 않음 */
  strncpy(array, str, 10); /* Good */

  pa = (char *)malloc(10);
  strncpy(pa, str, 10); /* Good : pa에 공간이 할당된 상태 */
  pa = str; /* Bad : 할당한 공간을 free하지 않고 다시 다른 값을 할당 */
}


이번에 보인 예제는 문자열의 처리와 관련하여 자주 범하게 되는 실수를 보여주고 있습니다. 특히나 생각하기 어려운 것 중의 하나는 문자열 배열( == char **, char [][], char *[] )의 경우인데, 특히 이런 경우에는 메모리를 할당하는 것을 잊고 코드가 작동하지 않아 고민하는 경우가 무척 많습니다.


int wrong_pchar_array()
{
  char *array[10];
  strcpy(array[0], “hello, jiny!”);
  /* Bad! : array[0]는 char *이며 할당되지 않은 공간 */
}


같은 내용의 코드는 strdup()를 이용하면 간편하게 작성할 수 있습니다. strdup()는 malloc()과 strcpy()를 합친 함수라고 할 수 있습니다.


int wrong_pchar_array()
{
  char *array[10];
  array[0] = strdup(“hello, jiny1!”);
  array[1] = strdup(“hello, jiny2!”);
  /* 함수를 종료하기 전에 반드시 free()해 줘야 한다 */
}


가장 고민을 많이 하는 경우는 배열을 사용하지 않고 두 개의 포인터로만 구성하는 경우입니다. 포인터 두 개를 이용한 문자열 배열을 만드는 코드를 적어두겠습니다.


#include

int main()
{
  char **ppchar=NULL;

  ppchar = (char **)malloc( sizeof(char *)* 10);
  memset(ppchar, 0, sizeof(char *) * 10 );

  ppchar[0] = (char *)malloc( sizeof(char) * 15);
  strncpy( ppchar[0], “hello, jiny!”, 14);

  ppchar[1] = strdup(“hello, jiny2!”);
  *(ppchar+2) = strdup(“hello, jiny3!”);

  free(ppchar[0]);
  free(ppchar[1]);

  printf(“ppchar[2] = %s\n”, ppchar[2]);
  free(ppchar[2]);
}


ppchar에는 포인터 타입의 변수를 동적 할당하는 것을 유심히 보아두기 바랍니다. 그리고 다시 ppchar[1]에 strdup()로 문자열을 할당한 것, 그리고 ppchar[2]를 포인터 변수를 이용해 접근한 부분을 유심히 보아 두기 바랍니다. 가장 이해가 되지 않을만한 부분은 *(ppchar+2) = strdup(“hello, jiny3!”)라는 부분이 될텐데, ppchar는 char **형이므로 *ppchar는 char *형이 됩니다. 따라서 문자열을 대입할 수 있는 공간인 것이지요. 여기에 보인 샘플만 이해한다면(아마 이해하기 쉽지 않으리라 생각합니다) 포인터와 관련된 대부분의 문제는 간단히 해결할 수 있습니다.
지금까지 몇 가지의 오류를 통하여 포인터를 사용하면서 범하기 쉬운 실수들에 대해 살펴봤습니다. 소개한 코드는 일부러 눈에 보이기 쉽게 만든 샘플이라는 것을 명심하세요. 실제로 부딪치는 오류들은 적은 것보다 훨씬 복잡한 모양이 됩니다. 하지만 독자 여러분들이 실제로 프로그램을 작성하면서 만나게 되는 대부분의 오류는 필자가 적어둔 잘못된 코드 어딘가에 있습니다. 앞에서 적어둔 잘못된 코드들을 완전히 이해한다면 막막하게만 느껴지던 포인터가 정리되어 가는 것을 느낄 수 있을 것입니다.  함수와 포인터
지금까지 포인터에 대해 간단하게 알아봤습니다. 하지만 포인터의 진가는 바로 함수 포인터를 자유자재로 사용할 때에야 알 수 있습니다. 함수 포인터는 ‘함수에 대한 포인터’입니다. 포인터는 주소를 가리키는 정수형 변수라고 앞에서 이야기하였지요. 보통 생각하는 포인터와 함수 포인터가 다른 점은 변수에 대한 포인터는 ‘데이터가 저장되어 있는 주소의 값’이지만, 함수 포인터는 ‘함수가 실행될 프로그램의 기계어 코드가 시작되는 주소의 값’이라고 생각할 수 있습니다. 어쨌든 주소의 값이라는 점에서는 다른 점이 없지요. 이 함수 포인터를 사용하면 아주 아름다운 코드를 많이 만들 수 있습니다. 역시 결론부터 말하면 ‘함수를 하나의 이름으로 호출하지만 다른 함수를 실행하기 위해’ 함수 포인터를 사용합니다.
필자는 꽤 오래 전 gnuplot이라는 프로그램의 소스코드를 읽어 본 적이 있습니다. 이 프로그램은 각종 함수에 대해 그래프를 그려주는 프로그램인데 필자에게 함수 포인터의 아름다움을 실감하게 해준 코드였습니다. 그래프를 화면에 출력하기 위해서는 무수히 많은 터미널들을 지원해야 하는데 gnuplot에서는 이 부분을 드라이버로 만들고 함수 포인터를 이용하여 중요한 코드는 모두 한 가지로만 구현하고 있습니다. 지금 생각해보면 지극히 당연한 일인데도, 당시 이 코드를 본 필자는 너무나도 아름다운 구조에 흥분해 있었던 기억이 납니다.
함수 포인터를 가장 간편하게 사용해 볼 수 있는 기회는 아마도 표준 C 라이브러리에 포함된 qsort()나 bsearch()와 같은 함수가 될 것입니다. 이 함수들은 정렬이나 검색에서 가장 중요한 역할을 하는 비교 함수를 함수 포인터로 전달할 수 있도록 해 놓았기 때문에 단순히 숫자나 문자열의 정렬이나 검색 이외에도 자신이 원하는 자료형을 자신이 원하는 방식대로 정렬하고 검색할 수 있도록 구성되어 있습니다.
함수 포인터가 많이 사용되는 또 한 가지의 경우라면 콜백함수를 들 수 있습니다. 콜백 함수란 ‘이러이러한 조건이 만족되면 그때쯤 이 함수를 실행하렴’이라는 의미로 미리 등록해두는 함수를 의미합니다. 꽤 어려워 보이지만 사실은 다음 qsort 예제에 사용된 compar_int 함수 등도 일종의 콜백에 속하는 것이라 생각해도 무방합니다. 실제로는 ‘타이머를 두고 5분 후에 이 함수를 실행시킬 것’과 같은 경우가 많습니다.
표준 C 라이브러리의 qsort 함수를 이용하여 배열의 원소를 정렬하는 예제를 간단히 적어봅니다. compar_int와 compar_int2만을 바꿔 주는 것으로 qsort라는 함수의 결과가 완전히 달라진다는 사실은 함수 포인터를 마음먹기에 따라 얼마나 효율적으로 사용할 수 있는지를 잘 보여줍니다.

#include

static
int compar_int2(const void *a, const void *b)
{
int x = *( (int *)a);
int y = *( (int *)b);

if ( x> y) return -1;
if ( x==y) return 0;
return 1;
}


static
int compar_int(const void *a, const void *b)
{
int x = *( (int *)a);
int y = *( (int *)b);

if ( x> y) return 1;
if ( x==y) return 0;
return -1;
}


int main()
{
int array[] = {10,3,5,2,1,6,51};
int i=0;

qsort(array, 7, sizeof(int), compar_int );

for (i=0; i< 7; i++)
printf(“array[%d] = %d\n”, i, array[i]);

qsort(array, 7, sizeof(int), compar_int2 );
for (i=0; i< 7; i++)
printf(“array[%d] = %d\n”, i, array[i]);

}


실행 결과
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 5
array[4] = 6
array[5] = 10
array[6] = 51
array[0] = 51
array[1] = 10
array[2] = 6
array[3] = 5
array[4] = 3
array[5] = 2
array[6] = 1
프로그래머는 코드로 이야기합니다. 필자가 즐겨 사용하는 함수 포인터를 이용한 테크닉 하나를 적어둡니다. 커맨드에 따라 적당한 함수를 호출하는 간단한 예제인데, do_cmd()에서와 같은 코드를 do_cmd_using_function_pointer()와 같은 코드로 고쳐서 사용하면 나중에 커맨드가 추가되더라도 테이블에 한 줄을 추가해 넣기만 하면 됩니다. if-else가 반복되는 것보다는 훨씬 깔끔한 코드가 되겠지요. 하지만 함수 포인터를 너무 복잡하게 사용하면 디버깅이 힘들어진다는 단점이 있기도 합니다.

#include

void _do_hello()
{
  printf(“command was HELLO\n”);
}


void _do_jiny()
{
  printf(“command was JINY\n”);
}


void _do_none()
{
  printf(“command was NONE\n”);
}


void do_cmd(const char *cmd)
{
  if ( strcmp(cmd, “hello”)==0 )
    _do_hello();
  else if ( strcmp(cmd, “jiny”)==0 )
    _do_jiny();
  else
    _do_none();
}


struct {
  const char *cmd;
  void (*cmd_handler)(void);
}

cmdtbl[] = {
  { “hello”, _do_hello},
  { “jiny”, _do_jiny},
  { NULL, _do_none},

};


void do_cmd_using_function_pointer(const char *cmd)
{
  int i=0;
  for (i=0; cmdtbl[i].cmd!=NULL; i++)
  {
    if ( strcmp(cmdtbl[i].cmd, cmd)==0 )
    {
      (*cmdtbl[i].cmd_handler)();
      return;
    }
  }
  (*cmdtbl[i].cmd_handler)();
  return;
}

main()
{
  const char *cmd=”hello”;


  printf(“Using IF-ELSE... \n”);
  do_cmd(cmd);


  printf(“Using Function Pointer Table\n”);
  do_cmd_using_function_pointer(cmd);
}  


  플러그인을 만들자
함수 포인터는 실제로 프로그램을 작성하면서 정말 많은 부분에서 유용하게 사용할 수 있습니다. 하지만 지금까지 본 내용에서는 실행되는 프로그램의 내부에 있는 함수만을 함수 포인터를 이용해 실행할 수 있었지요. 이제 프로그램을 수정하지 않고 외부의 모듈을 추가해 프로그램의 기능을 확장시키는 방법을 알아보겠습니다.
많은 독자들은 프로그래밍을 하면서 MP3 음악 파일을 즐겨 듣겠지요. 이 MP3 재생 프로그램 중 많이 사용하는 플레이어로 윈앰프(winamp)를 들 수 있습니다. 그리고 윈앰프의 강력한 기능은 대부분 플러그인 덕분이라고 해도 과언이 아닙니다. 윈앰프라는 프로그램 자체는 단지 플러그인을 처리해주기 위한 본체에 불과합니다. MP3 파일을 재생하고 음악 CD를 재생하고 재생하는 음악을 3차원의 멋진 동영상으로 표시해 주고, 듣고 있는 음악을 3차원 음향으로 바꿔 출력하는 모든 기능은 각각 별도의 플러그인이 담당해주는 것이지요. 플러그인을 추가할 때마다 winamp.exe라는 기본 프로그램은 전혀 변하지 않지만 우리가 사용하는 윈앰프의 기능은 점점 더 많아집니다. 바로 이것이 플러그인의 위력이라고 할 수 있습니다.
플러그인은 사실 앞에서 적어둔 함수 포인터를 이용해 커맨드 핸들러를 추가하는 방법과 원리에서 크게 다르지 않습니다. 단 몇 가지의 다른 점이 있습니다. 플러그인은 보통 별도의 파일인 것이 대부분입니다. 동적으로 로딩을 위해 dll이나 shared library로 구현되며 플러그인을 로딩하는 쪽에서는 Win32와 유닉스에서 각각 LoadLibrary()나 dlopen()을 사용하여 라이브러리를 로드하고 로드한 라이브러리에서 이름으로 함수 포인터를 얻어내기 위해 GetProcAddess()나 dlsym()을 이용합니다. 함수 포인터를 얻어내면 적당한 함수 포인터형 변수에 이 포인터를 대입하고 실행해주기만 하면 됩니다. 복잡해 보이는 플러그인을 구현하기 위한 내용은 이것이 전부입니다. 믿어지지 않는다고요? 믿으면 됩니다. 윈앰프도 이 방법으로 만든 프로그램이 확실하니까요.
필자는 리눅스 PDA 요피(Yopy)에서 작동하는 애플리케이션 요피투데이(YopyToday)를 개발한 적이 있습니다. 포켓PC(PocketPC)에서 흔히 볼 수 있는 오늘의 일정을 요약해 주는 첫 화면에 해당하는 프로그램인데 바로 이 요피투데이는 여러 가지의 플러그인을 이용해 기능을 확장시키도록 구성되어 있습니다. 요피투데이의 소스코드는 http://kldp.net/ projects/yopytoday/에서 언제든지 다운받을 수 있습니다. 실제로 GUI 프로그램을 어떻게 플러그인을 이용해 구현할 수 있는지 궁금한 독자라면 요피투데이의 소스코드를 읽어보면 많은 도움이 되리라 믿습니다.

다음은 자주 범하는 오류에 대해서
이번 호에서는 포인터에 대해 알아보았습니다. 무척 편리하기도 하지만 포인터는 프로그램이 크래시하는 가장 큰 원인의 하나입니다. 다음 시간에는 포인터를 사용한 프로그램을 작성하면서 C 프로그래머라면 반드시 지켜야 할 일들을 알아보도록 하겠습니다. 물론 C 프로그래머가 자주 범하는 오류에 대해서도 알아보도록 하지요. 바로 ‘에러없는 프로그램 작성하기’입니다.