Skip to main content

포인터 기초

Address(주소)

  • Memory(메모리)는 저장 장소의 위치를 나타내는 Address 값을 가진다.
  • 이 Address(주소)는 1Byte(바이트)마다 1씩 증가하는 연속적인 번호로 구성된다.
  • Address 값은 보통 16진수로 표현한다.

Address Operator(주소 연산자) &

  • 변수의 Address 값을 얻기 위해서는 변수 앞에 Ampersand(&) 기호를 사용하는 Address Operator를 이용한다.
  • 변수 age가 있다면, &ageage 변수의 Address 값을 반환한다.
  • 이 Address 값을 printf 함수로 출력하려면 %p 변환 명세를 사용한다.
  • Address 값을 이용하면 더 편리하고 유연한 프로그램 작성이 가능하지만, 코드가 복잡하고 어려워지는 단점이 있다.

address.c 예제

아래 코드는 char, int, double 자료형 변수들의 값과 Address를 출력하는 예제이다.

#include <stdio.h>

int main(void)
{
char ch = 'a';
int num = 100;
double real[2] = {3.14, 1.87};

printf("char 변수(ch) 값 %6c의 주소는 %p이며, 십진수로 %8u이다.\n", ch, &ch, &ch);
printf("int 변수(num) 값 %6d의 주소는 %p이며, 십진수로 %8u이다.\n", num, &num, &num);
printf("\n변수 ch와 변수 num의 주소 값의 차이는 %u이다.\n\n", (unsigned)&ch - (unsigned)&num);

printf("double 변수(real[1]) 값 %6.2F의 주소는 %p이며, 십진수로 %8u이다.\n", real[1], &real[1], &real[1]);
printf("double 변수(real[0]) 값 %6.2F의 주소는 %p이며, 십진수로 %8u이다.\n", real[0], &real[0], &real[0]);
printf("\n변수 num과 변수 real[0]의 주소 값의 차이는 %u이다.\n\n", (unsigned)&num - (unsigned)&real[0]);

return 0;
}
  • 변수들의 Address 값은 서로 다르며, 그 차이는 각 자료형의 크기와 관련이 있다.

Little Endian(리틀 엔디안)과 Big Endian(빅 엔디안)

Memory에 여러 Byte로 구성된 데이터(예: int형)를 저장하는 방식에는 두 가지가 있다.

  1. Little Endian: 데이터의 가장 작은 단위(최하위 바이트)가 낮은 Address에 저장되는 방식이다.
  2. Big Endian: 데이터의 가장 큰 단위(최상위 바이트)가 낮은 Address에 저장되는 방식이다.

Pointer Variable(포인터 변수)

  • 일반 변수와 달리 Memory의 Address 값만을 저장할 수 있는 특별한 변수이다.
  • Pointer Variable(포인터 변수)를 사용하면 프로그램이 간결하고 효율적으로 될 수 있다.

예를 들어, int i = 3;이라는 변수가 있고, 이 변수의 Address가 12FF7C라고 한다면, 이때 int *pi = &i;와 같이 선언할 때 pi라는 Pointer Variable은 변수 i의 Address 값인 12FF7C를 저장하게 된다.

Pointer Variable의 선언과 값

  • Pointer Variable은 가리키는 변수의 자료형에 맞춰 선언해야 한다. *(Asterisk) 기호는 해당 변수가 Pointer Variable임을 나타낸다.

    • int *ptr;: ptrint형 변수의 Address를 저장하는 Pointer Variable이다. *ptr 자체는 int형 변수로 생각할 수 있다.
  • 여러 개의 Pointer Variable을 선언할 때는 각 변수명 앞에 *를 붙여야 한다.

    • int *ptr1, *ptr2, *ptr3;
  • Pointer Variable은 Address 값만 저장할 수 있으며, 선언과 동시에 특정 변수의 Address로 초기화할 수 있다.

    • int i = 3;
    • int *ptr = &i;
  • 위 코드에서 ptri의 Address를 가지므로, *ptr은 변수 i 자체를 의미하게 되어 *ptr의 값은 3이다.

Dereference Operator(역참조 연산자) *

  • Pointer Variable 앞에 *를 붙이면, 해당 Pointer Variable이 가리키는 Memory 공간에 저장된 값에 접근할 수 있다. 이것을 Dereference(역참조)라고 한다.
  • 예를 들어, *ptr = i + 2;라는 구문은 ptr이 가리키는 변수(즉, i)의 값을 i + 2로 변경하라는 의미이다.

배열과 포인터

  • 배열의 이름은 그 자체로 배열의 첫 번째 원소의 Address를 나타내는 주소 상수(constant)이다.
    • int point[] = {95, 88, ...}; 에서 point&point[0]과 같다.
  • 따라서 배열 이름을 Dereference하면 첫 번째 원소의 값이 된다.
    • *pointpoint[0]과 같다.
  • 포인터 연산을 통해 다른 배열 원소에도 접근할 수 있다.
    • point + 1은 두 번째 원소(point[1])의 Address를 의미한다. (&point[1])
    • 이때 실제 Address 값은 (point의 Address) + sizeof(int) 만큼 증가한다.
  • 일반적으로 *(point + i)point[i]와 동일하다.

함수 인자로서의 포인터

함수의 인자로 배열이나 포인터를 전달하는 방식을 **Call by Address(주소에 의한 호출)**라고 한다.

배열 합계 함수 예제

배열의 합을 구하는 함수는 다음과 같이 두 가지 방식으로 선언할 수 있다.

  1. 배열 형태로 인자 받기: int sumaryf1(int ary[], int SIZE)

    • 함수 내부에서 ary[i] 또는 *(ary + i) 형태로 원소에 접근할 수 있다.
  2. 포인터 형태로 인자 받기: int sumaryf2(int *ary, int SIZE)

    • 함수 내부에서 *ary++와 같이 포인터를 직접 증가시키며 원소에 접근할 수 있다.

함수를 호출할 때는 배열의 이름이나 첫 번째 원소의 Address를 넘겨준다.

  • sum = sumary(point, 6);
  • sum = sumary(&point[0], 6); // 위와 동일

Call by Value(값에 의한 전달) vs Call by Address

  • Call by Value: 일반 변수를 인자로 넘기는 방식으로, 함수 내에서 인자 값을 변경해도 원본 변수에는 영향을 주지 않는다. 값의 복사가 일어나기 때문이다.
  • Call by Address: 변수의 Address를 인자로 넘기는 방식으로, 함수 내에서 포인터를 Dereference하여 원본 변수의 값을 직접 수정할 수 있다.

Swap 함수 비교

  • Call by Value 사용 시: 두 변수의 값이 바뀌지 않는다.
  • Call by Address 사용 시: 포인터를 이용해 원본 변수의 값을 직접 교환하므로 값이 성공적으로 바뀐다.

포인터 연산의 제약

  • 배열의 이름은 주소 상수(constant)이므로, 값을 변경할 수 없다. 따라서 point++와 같은 증감 연산은 컴파일 오류를 발생시킨다.

  • 하지만 배열의 Address를 다른 Pointer Variable에 복사한 후에는 해당 Pointer Variable을 증가시키는 것이 가능하다.

    int point[] = { ... };
    int *pi = point; // point는 상수, pi는 변수
    sum += *(pi++); // 가능

문자열과 포인터

  • char 배열로 선언된 문자열에서 &name[i]i번째 문자부터 시작하는 새로운 문자열의 Address를 의미한다.
  • char *pary[]와 같이 문자 포인터 배열을 사용하면 여러 개의 문자열을 효율적으로 관리할 수 있다. 각 배열 원소는 문자열 리터럴의 시작 Address를 저장한다.

Command Line Argument(명령어 줄 전달인자)

  • 프로그램을 실행할 때 명령어 줄(command line)을 통해 문자열 인자를 전달할 수 있다.

  • main 함수를 int main(int argc, char *argv[]) 형태로 선언하여 인자를 받는다.

    • argc: 전달된 인자의 개수 (프로그램 이름 포함)

    • argv: 인자 문자열들을 가리키는 포인터들의 배열 (char *[])

      • argv[0]: 프로그램 이름
      • argv[1]: 첫 번째 인자
      • argv[2]: 두 번째 인자, ...
  • 개발 환경(예: Visual C++)에서는 프로젝트 속성에서 디버깅 시 사용할 Command Line Argument를 미리 설정할 수 있다.