C/C++

[C언어와 알고리즘] 포인터와 배열 (5)

OSS 2012-08-17 17:10:33 29918
2011


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

연재 차례

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

C 언어에서 포인터(pointer)는 중요한 개념이다. 포인터는 변수이다. 포인터는 변수의 주소(address)를 보관하는 변수이다. 포인터는 C언어에서 간결하고 효율적인 코드를 작성하는데 많이 사용된다. 다른 방식으로는 풀리지 않는 문제들을 포인터를 사용하면 쉽게 해결할 수 있는 경우가 있다.  포인터는 배열(arrays)과 밀접하게 연관되어 있으며, 이들의 관계를 이해하는 것은 중요하다. goto 문장과 마찬가지로, 포인터를 주의 깊게 사용하지 않으면 예상하지 못하는 위치를 가리키게 되고 프로그램이 난해해진다.

그러나 포인터의 기본원리를 충분히 익혀두면, 프로그램을 좀더 명확하고 간결하게 작성하는데 많은 도움이 된다. C언어는 그 동안 훌륭한 프로그래머들의 노력과 좋은 컴파일러들에 의해서 포인터를 좀 더 명확하게 다루는 방법들에 대한 규칙을 만들었다. 이것을 이해해 보도록 하자.

포인터(Pointer)와 주소(Address)

포 인터는 메모리와 밀접하게 연관되므로 메모리에 데이터를 보관하기 위한 규정에서부터 시작해 보자. char 형의 데이터는 byte(8비트) 크기 만큼의 메모리 공간을 차지하고, short int는 char형 2개를 합친 만큼, int는 char형 4개를 합친만큼, long 혹은 double 형은 char형 8개를 합친 만큼의 메모리 공간을 차지한다. 바이트를 합치는 개수는 기계(아키텍쳐)들마다 조금씩 차이가 있을 수 있지만, 이렇게 합쳐진 메모리 공간(addressed memory cells)마다 주소가 부여된다.  포인터는 이러한 주소를 보관한다.

예를 들면, c라는 char형 변수가 있고, p는 c(바이트 크기의 메모리)를 가르키는 포인터라면 아래와 같은 방식으로 표현된다.

 p = &c;

위 에서 &는 c의 주소를 가져오는 단항 연산자(unary operator)이다. 이렇게 해서 c의 주소를 포인터 변수 p가 보관한다. p는 c를 `가르키고 있다(point to)` 라고도 한다. 단항 연산자 &는 메모리 안의 객체(변수나 배열)에만 적용된다. 즉, 표현식, 상수, 레지스터 변수에는 적용할 수 없다. 또한 단항 연산자 *가 있는데, 이것은 주소 안의 내용을 참조하는 연산자이며 포인터에 적용될 수 있다. 예를 들어, x와 y는 정수형 변수이고 ip는 int에 대한 포인터라고 하면, 아래와 같이 &와 *를 사용할 수 있다.

   int x = 1, y = 2, z[10];
   int *ip;    //ip는 int에 대한 포인터이다(ip is a pointer to int)
   ip = &x;    //ip는 x를 가르킨다(ip points to x)
   y = *ip;    //y는 1이 된다
   *ip = 0;    //x는 0이 된다
   ip = &z[0];    //ip는 z[0]를 가르킨다(ip points to z[0])

위에서 int x = 1, y = 2, z[10];는 일반변수를 정의하는 부분이고, int *ip; 이하는 포인터에 대한 작업들이다. 포인터는 자기가 가르키고 있는 데이터형에 따라서 동작한다.

   *ip = *ip + 10;

위에서 ip가 integer형의 x를 가리키고 있으므로, 위의 연산식은 x와 같은 특성으로 동작한다. 즉, *ip는 10만큼 더해진다. 단항 연산자 *와 &는 산술연산자보다 연산 우선순위가 높다.

   y = *ip + 1;

위의 수식은 ip가 가르키고 있는 값을 먼저 가져온 후, 1을 더하여 y에 할당한다. 아래 수식은 ip가 가르키고 있는 값에 1만큼 더하는 것이다.

   *ip += 1;

   ++*ip;

그러나 아래는 위의 것과 다르다.

    *ip++;

왜냐하면, 단항 연산자 ++ 와 *는 연산 우선방향이 오른쪽에서 왼쪽으로 진행되기 때문에 ip++가 먼저 연산되고 *ip가 참조되므로 아래와 같이 ( )를 사용하여 연산 우선순위를 조정해 주어야 한다.

   (*ip)++;

포인터 또한 변수이기 때문에, 다음과 같이 포인터를 또 다른 포인터 변수에 할당할 수 있다.

   iq = ip;

이렇게 하면 ip가 가지고 있는 내용을 iq도 동일하게 가르키게 된다.

포인터와 함수 매개변수
C언어는 함수의 매개변수에 값을 전달하기 때문에 호출된 함수 내에서 호출한 함수의 변수값을 직접적으로 변경하지 못한다. 예를 들어, 두 개의 변수 값을 교환하고자 하는 아래의 함수를

   void swap (int x, int y)
   {
    int temp;

    temp = x;
    x = y;
    y = temp;
   }

swap(a, b)로 호출하면, swap 함수에는 a, b 값이 넘어가서 서로의 값을 교환하고자 하는 것이지만, swap 함수 내부에서만 값의 교환이 발생하고 swap 함수를 호출한 곳에서는 실제적으로 값이 교환되지 않는다. 호출한 곳에서 a, b값을 실제적으로 교환 하려면 아래와 같이 swap 함수의 매개변수가 주소를 넘겨받을 수 있도록 포인터 변수로 정의한다.

   void swap (int *px, int *py)
   {
    int temp;

    temp = *px;
    *px = *py;
    *py = temp;
   }
옆 의 함수를 swap(&a, &b)로 호출하여 a, b 변수의 주소를 넘겨주면(&는 주소 연산자), swap 함수는 이 변수들의 주소를 받아서 그 주소 안에 있는 값을 교환하면, swap 함수를 호출한 곳의 a, b 변수 값도 실제적으로 교환된다.

포인터와 배열

C언어에서 포인터와 배열은 아주 밀접하게 연관된다. 배열로 수행할 수 있는 동작들은 포인터로도 표현할 수 있다. 일반적으로 포인터가 배열보다 빠르게 동작하지만, 배열이 포인터보다 이해하기 쉽다.

   int a[10];

위 의 문장은 정수형 배열을 선언한 것이며 배열 요소는 10개이다. 각각의 배열요소는 a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]로 접근한다. 따라서 a[i]는 배열 a의 i번째 요소를 의미한다. 정수형을 가르키는 포인터 변수는 다음과 같이 선언한다.

   int *pa;

그리고, 배열 a의 0번째 요소의 주소를 pa에 할당한다. 즉 포인터 변수 pa는 a[0]의 주소를 가지게 된다.

   pa = &a[0];

그런 다음, pa안에 있는 내용(값)을 x 변수에 복사(저장)한다.

   x = *pa;

포 인터 변수 pa는 배열 요소중의 하나를 가리킨다. pa+1은 pa에서 1번째 요소의 주소이다.  따라서, pa+i는 pa에서 뒤로 i번째 요소의 주소이고, pa-i는 pa에서 앞으로 i번째 요소의 주소이다.  pa는 a[0]의 주소이다.

   *(pa+1)

위 의 표현은 배열 a[1]의 내용(값)을 가져온다. pa+i은 a[i]의 주소이고 *(pa+i)는 a[i]의 내용(값)이다. 이러한 규칙은 배열 a의 데이터 타입이나 크기에 상관없이 적용 가능하다. 포인터 연산에서 포인터에 1을 더한다는 것은 다음 대상을 가르킨다는 의미이다. 즉, pa+i는 pa로부터 i번째 대상을 가르킨다.(pa에서 i만큼 떨어져 있는 주소) 배열요소 인덱스와 포인터 연산은 매우 밀접하게 연관되어 있다.

   pa = &a[0];

배열 a의 첫 번째 요소인 a[0]의 주소는 배열의 이름과 동일하기 때문에 위의 문장을 다음과 같이 표현할 수 있다.

   pa = a;

a[i] 와 *(a+i)는 동일하므로, C언어는 a[i]를 *(a+i)로 즉시 변환한다. 또한 &a[i]는 a+i와 동일하다. a+i는 a로부터 i번째 요소의 주소이다. 따라서 배열과 인덱스의 조합은 포인터와 오프셋(offset) 조합으로 표현할 수 있다.

배 열명과 포인터 사이에는 한가지 주의해야 할 점이 있다. 포인터 pa는 변수이므로 pa=a 와 pa++와 같은 표현이 가능하다. 그러나 배열명은 변수가 아니므로 a=pa 와 a++와 같은 표현은 불가능하다. 배열명이 함수의 파라미터로 전달 될 때는 배열의 첫 번째 요소의 위치가 전달된다. 호출된 함수 내에서는 파라미터가 내부 변수이므로 전달된 배열명은 포인터 변수가 된다. 다음 예제를 통하여 이 내용을 익혀보자.

   /* strlen: 문자열 s의 길이를 구함 */
   int strlen (char *s)
   {
    int n;

    for (n = 0; *s != ‘

맨 위로
맨 위로