C Study 0008

C언어 공부 0008

포인터

포인터란

  • 메모리의 주소를 가진 변수
    • 변수의 주소는 &연산자를 이용하여 알 수 있다.
  • 포인터를 통해 메모리에 직접 접근이 가능하다.
    • 메모리는 바이트 단위로 access된다.
  • 자료형에 따라 메모리에서 차지하는 공간이 다르다.
    • 이전의 변수 관련 포스트를 참고하세요.
  • 포인터 변수의 선언은 *연산자를 변수명 앞에 붙여주는 것으로 사용이 가능하다.
    • int *p;와 같이 사용
int i = 10;
int *p; // 포인터 변수 선언
p = &i; // 변수 i의 주소가 포인터 p로 대입
  • 위 예시에서 p는 변수를 가리키는 포인터이며, *p의 값은 10이 되게된다.

포인터 사용시 주의점

  • 초기화가 안된 포인터를 사용하면 런타임 에러가 발생한다.
int *p; // 포인터 p는 초기화가 안되어 있음
*p = 100; // 에러발생
  • 포인터가 아무것도 가리키고 있지 않는 경우에는 NULL로 초기화해야 한다.
    • NULL 포인터를 가지고 간접 참조하면 하드웨어로 감지할 수 있다.
  • 포인터의 타입과 변수의 타입은 일치하여야 한다.

포인터 연산

  • 증가, 감소, 덧셈, 뺄셈 연산이 가능하며, 포인터가 가리키는 객체 크기만큼 값이 바뀌게 된다.
    • 예를들어 int형의 경우 4, char형의 경우 1 만큼 바뀐다.
#include <stdio.h>

int main(void)
{
    char *pc;
    int *pi;
    double *pd;

    pc = (char *)10000;
    pi = (int *)10000;
    pd = (double *)10000;

    printf("증가 전 pc = %d, pi = %d, pd = %d\n", pc, pi, pd);

    pc++;
    pi++;
    pd++;

    printf("증가 후 pc = %d, pi = %d, pd = %d\n", pc, pi, pd);
    printf("pc+2 = %d, pi+2 = %d, pd+2 = %d\n", pc+2, pi+2, pd+2);

    return 0;
}
  • 위 예시의 결과는 다음과 같다.
증가 전 pc = 10000, pi = 10000, pd = 10000
증가 후 pc = 10001, pi = 10004, pd = 10008
pc+2 = 10003, pi+2 = 10012, pd+2 = 10024
  • char형인 pc는 증가를 1*1, 1*2만큼, int형인 pi는 증가를 4*1, 4*2 만큼, double형인 pd는 증가를 8*1, 8*2만큼 하는 모습을 볼 수 있다.
  • *연산자와 증감연산자 중에서 증감연산자가 우선순위가 더 높다.
수식 의미
v = *p++; p가 가리키는 값을 v에 대입한 후에 p를 증가한다.
v = (*p)++; p가 가리키는 값을 v에 대입한 후에 가리키는 값을 증가한다.
v = *++p; p를 증가시킨 후에 p가 가리키는 값을 v에 대입한다.
v = ++*p; p가 가리키는 값을 가져온 후에 그 값을 증가하여 v에 대입한다.

포인터와 배열의 관계

  • 포인터와 배열은 아주 밀접한 관계를 가질 수 있다.
  • 배열은 메모리상에 연속적으로 자료가 저장되는 구조이고, 포인터는 메모리에 주소를 통해 직접 접근할 수 있기 때문이다.
#include <stdio.h>

int main(void)
{
    int a[] = {10, 20, 30, 40, 50};

    printf("a = %u\n", a);
    printf("a + 1 = %u\n", a + 1);
    printf("*a = %d\n", *a);
    printf("*(a+1) = %d\n", *(a+1));

    return 0;
}
  • 위 예시의 결과는 다음과 같다.
a = 1245008
a + 1 = 1245012
*a = 10
*(a+1) = 20
  • 배열의 이름 a의 주소와 a+1의 주소가 4만큼 차이나는 것을 통해, 우리는 int형 배열의 한 칸이 4만큼의 공간을 차지하는 것을 알 수 있고, 배열은 메모리에 연속적으로 값이 저장되는 구조이므로, 이를 통해 배열의 각 원소에 접근할 수 있다는 힌트를 얻을 수 있다.
  • 그리고 *a값이 10, *(a+1)의 값이 20인 것을 보아 a의 주소는 배열의 첫 원소를 가리키고, 포인터 연산을 통해 다음 원소에도 접근할 수 있다는 것을 알 수 있다.

함수 호출 시 인수 전달 방법

  • call by value : 인수의 값이 복사되어 전달
  • call by reference : 포인터를 이용하여 흉내내는것으로, 인수의 주소값이 복사되어 전달
    • 배열 또한 이 방식을 따른다.

Swap 함수 작성해보기

  • 잘못된 예
void swap(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}
  • 위 코드를 이용하여 main함수에서 두 값을 바꾸게 되면, 값이 바꿔지지 않는다. 그 이유는, 기본적으로 C언어는 call-by-value에 의한 argument value를 전달하므로, swap함수 안에서만 값이 바뀌고 이 결과가 main함수에 반영되지 않는것이다.
  • 올바른 코드
void swap(int *x, int *y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

포인터를 반환하는 함수

  • 함수가 종료되더라도 남아 있는 변수의 주소를 반환하여야 한다.
  • 함수의 지역변수 주소를 반환하면, 함수가 종료되면 사라지기 때문에 오류가 발생한다.

함수 포인터

  • 포인터는 함수를 가리킬 수 있다.
    • 함수 또한 메모리상에서 공간을 가지고, 코드로 존재하기 때문이다.
  • 형태
자료형 (*함수포인이름)(매개변수);
#include <stdio.h>
int add(int a, int b);

int main()
{
    int (*pf)(int a, int b);
    pf = add;
        .
        .
        .
}

Reference

0%