(C) 포인터 사용법

C/C++

Posted by kwon on 2019-10-11

이번 학기에 배우는 영상처리에서 C언어를 이용하여 작업을 진행하다가 포인터에 대한 추가적인 이해가 필요하다고 생각되어 내가 C언어에서 부족하다고 생각했던 부분들을 다시 공부해 볼 생각이다. C언어 하면 포인터이기 때문에 포인터의 기초부터 다시 리뷰해보자.

포인터란?

메모리

  • 프로그램이 실행되기 위해 필요한 정보(값)을 저장하는 공간
  • 1byte(8 bit) 단위로 물리 주소가 부여되어 있음
  • 개념적으로, 메모리는 일렬로 연속되어 있는 크기가 1byte인 방들의 모음이라고 볼 수 있음
  • 일반적으로 주소의 길이는 4bytes이고, 주소는 16진수로 표현

포인터

  • 주소를 나타내는 특수 자료형

  • 일반적으로 변수의 주소를 저장하며, “변수를 가리킨다”라고 표현함

  • 주소는 숫자이므로, 기본적으로 정수로 표현됨. 하지만, int(정수형 자료형)와 구별되어 처리됨(다른 자료형)

  • 따라서 선언할 때 포인터임을 명시해야 한다.

  • 선언 방법 : 변수 명 앞에 *(참조연산자)만 붙이면 된다.

ex) char * pch;

int * pnum;

-> pch와 pnum은 똑같이 주소를 저장하는 변수지만 자료형이 다르므로 다른 자료형으로 취급

대입

  • 포인터 (변수)에는 주소
    만 대입될 수 있다.
  • 포인터 (변수)에 주소를 대입하여 특정 변수와 연결시키는 것을 “가리킨다”라고 표현하고, 그림에서는 화살표 ->로 표시
  • 참조 연산 : 포인터 (변수)가 가리키는 변수에 접근하는 것
  • 참조연산자 * (간접연산자, 포인터연산자 라고도 부름)를 사용

ex) *pch : 포인터 pch가 가리키는 변수, 0x3C번지에 저장된 값

1
2
3
4
5
6
7
8
9
10
char ch = 'A', * pch;
int num = 3, * pnum;

pch = &ch; // 포인터 pch에 변수 ch를 연결시킴
pnum = # // 포인터 pnum에 변수 num을 연결시킴
// 반드시 어떤 변수에 연결 후 사용

printf("%c %p\n", *pch, pch);
printf("%d %p\n", *pnum, pnum);
// %p는 printf 에서 16진수로 주소를 출력해준다.

결과:
A 001EA03C

3 001EA042

1
2
3
4
5
6
7
8
9
10
11
12

int num1, num2, * p = &num1; //int 변수 num1, num2 선언, int 포인터 변수 p 선언 및 num1의 주소로 초기화

*p = 3000; // p가 가리키는 변수에 3000 대입
num2 = *p; // num2에 p가 가리키는 변수값 대입
p = &num2; // p가 num2를 가리키도록 변경

*p = *p -1000; // p에 연결된 변수에 p가 가리키는 변수 값 – 1000 대입
num1 = *p * 2; // num1에 p가 가리키는 변수 값의 2배 대입

printf("num1 = %d num2 = %d p = %p\n", num1, num2, p); // num1, num2, p를 출력핚다.
printf("num1 = %p num2 = %p p = %p\n", &num1, &num2, &p); // num1의 주소, num2의 주소, p의 주소를 출력핚다

결과 :
num1 = 4000 num2 = 2000 p = 004FF940

num1 = 004FF94C num2 = 004FF940 p = 004FF934

배열과 포인터

배열의 이름은 배열의 시작 주소를 의미한다. 즉, int ar[10] = {0}; 이라는 배열이 있을때 ar = &ar 과 같은 의미이다.

  • 주소를 이용한 배열 참조
    배열 이름은 주소를 의미하므로, 참조 연산자와 함께 사용이 가능하다.

ar : 배열의 시작 주소

*ar : 배열의 시작 주소에 저장된 값, 즉 0번째 원소를 의미

  • 배열 주소에 대한 증감 연산 (ar의 값이 0xB4일 때)

    ar + 1 의 결과는? 0xB5? 0xB8?

    *(ar + 1)의 결과는 ?

1
2
3
int ar[5] = {2, 3, 5, 7, -1};

printf("%p %d %d\n", ar+1, ar[1], *(ar+1));

결과 : 001E40B8 3 3

주소에 1을 더하면, 변수의 크기만큼 주소가 증가한다.

  • 배열 주소에 대한 증감 연산은 배열 원소 하나의 크기 만큼 증가 or 감소

int 배열의 경우 : 4

ar + i : 배열 ar의 i번째 원소의 주소

*(ar + i) : 배열 ar의 i번째 원소의 값, 즉, ar[i]

  • 배열의 시작 주소를 이용하여 배열을 포인터 형태로 사용할 수 있다.

  • 주소의 증감 단위가 변수의 크기에 의해 결정되므로 배열의 자료형에 관계없이 *(ar + i) = ar[i] 가 성립한다.

  • 배열을 포인터 변수에 연결하여 사용
    배열 이름은 주소를 의미하므로, 포인터 변수에 대입이 가능

    1
    2
    int ar[5] = {2, 3, 5, 7, -1};
    int *p = ar;

    위와 같이 사용이 가능하며 포인터 변수도 배열의 첨자 형태로 값을 참조 할 수 있다. 즉 p[0] = *p이고 p[1] = *(p + 1) 이다.

포인터 인자와 주소 반환

  • 두 변수를 교환하는 swap함수를 만들어보자
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void swap(int x, int y)
    {
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
    }
    void main()
    {
    int x = 10, y = 20;

    swap(x, y);

    printf("%d %d", x, y);
    }
    실행 결과 : 10 20

main 함수의 변수가 왜 안 바뀌는 지 메모리 그림을 확인해보자.

- 포인터를 swap함수의 인자로 사용하도록 수정해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void swap(int *px, int *py)
{
int tmp;
tmp = * px;
px = * py;
* py = tmp;
}
void main()
{

int x = 10, y = 20;

swap(&x, &y);

printf("%d %d", x, y);
}

실행 결과 : 20 10

이렇게, 함수의 인자로 변수의 주소값을 넘겨주고 swap함수에서 포인터 변수로 처리를 하게 되면 값이 정상적으로 바뀌는 것을 확인할 수 있다.

  • main함수의 값이 왜 바뀌는 지 메모리 그림을 확인해보자