본문 바로가기

프로그래밍/C

[C]포인터의 이해와 활용-2

다양한 자료형의 포인터 선언

const int num = 10; // int형 상수
const int *numptr;  // int형 상수를 가리키는 포인터
                    // int const *numptr과 같은 의미다.
numptr = #
*numptr = 20;		// 컴파일 에러 발생. num이 상수이므로 역참조하여 값을 변경할 수 없음.

위 코드들에서는 'int형' 포인터들을 선언하였다.

하지만 포인터에는 그보다 훨신 다양한 자료형들이 있는데, 심지어 'void'형 자료형도 있다.

#include <stdio.h>

int main()
{
    long long *numptr1;     // long long형 포인터 변수 선언
    float *numptr2;         // float 형 포인터 변수 선언
    char *cptr;             // char 형 포인터 변수 선언
    
    long long num1 = 10;
    float num2 = 3.14f;
    char c = 'A';
    
    numptr1 = &num1;        // 포인터 변수 numptr1에 num1의 주솟값 저장
    numptr2 = &num2;        // 포인터 변수 numptr2에 num2의 주솟값 저장
    cptr = &c;              // 포인터 변수 cptr에 c의 주솟값 저장
    
    retuen 0;
}

위 코드에서도 보았다 싶이 자료형마다 각각의 포인터 변수들을 선언할 수 있다.

조금 더 확장하자면, C에서 사용할 수 있는 모든 자료형으로 포인터 변수를 선언할 수 있다.

각각의 자료형 포인터의 존재 이유?

하지만 왜 이렇게 굳이 자료형마다 포인터를 선언하게 만들었을까?

근본적인 이유는, 바로 어떤 자료형으로 포인터 변수를 선언하느냐에 따라서 메모리 접근 방법이 달라지기 때문이다.

int num;

가령, int형 변수를 선언하였다고 가정하자.

sizeof(num);

자료형 int는 4byte의 크기를 메모리 상의 공간에 정적할당한다.

int *numptr = &num;
sizeof(*numptr);

그렇다면 int형 포인터 변수를 사용하여 역참조한다면 어떻게 될까?

printf("%d", sizeof(num));
printf("%d", sizeof(*numptr));

위 두 출력 코드는 동일한 '4'라는 값이 출력된다.

여기서 알 수 있는 것은 포인터 변수들의 자료형은 선언한 자료형의 크기에 맞춰서 값을 가져오거나(참조하거나) 저장하게 된다.

변수 num은 int형으로 선언하였고, int형의 메모리상의 할당 크기는 4byte이다.

마찬가지로 numptr는 int 포인터형으로 선언하였고, 참조 혹은 저장할 때 할당받는 메모리상의 크기가 4byte이다.

또한 역참조를 할 때에도 참조 크기가 4byte임을 위 코드에서 알 수 있다.

<출처> 코딩 도장, Unit 34.2 다양한 자료형의 포인터 선언하기, 그림34-23

void 포인터의 선언

위 포인터들은 모두 자료형이 존재하는 포인터이다.

long long *numptr1  // long long형 포인터
float *numptr2      // float형 포인터

하지만 자료형이 정해지지 않은 포인터도 존재한다.

void *numptr    // 자료형이 정해지지 않은(void) 포인터

기본적으로 C와 C++은 자료형이 다른 포인터끼리 메모리 주소를 저장하는 컴파일 경고가 발생한다.

하지만 void 포인터는 자료형이 정해지지 않았다는 특성 덕분에, 어떠한 자료형으로된 포인터들과도 대응될 수 있다.

반대로 다양한 자료형으로 된 포인터에도 void 포인터를 저장할 수 있다.

int *numptr = &num;
char *cptr = &c;
void *ptr;          // void 포인터의 선언 방식

// 저장되는 포인터의 자료형이 달라도 컴파일 경고가 발생하지 않음
ptr = numptr;       // void 포인터에 int 포인터 저장
ptr = cptr;         // void 포인터에 char 포인터 저장

// 저장하는 포인터의 자료형이 달라도 컴파일 경고가 발생하지 않음
numptr = ptr;       // int 포인터에 void 포인터 저장
cptr = ptr;         // char 포인터에 void 포인터 저장

이러한 특성 덕분에 'void 포인터' '범용 포인터' 라고도 한다.

즉, 직접 자료형을 변환하지 않더라도 암시적으로 자료형이 변환되는 방식이다.

<출처> 코딩 도장, Unit 34.2 다양한 자료형의 포인터 선언하기, 그림34-24

void 포인터의 역참조

하지만 void 포인터는 자료형이 정해지지 않았으므로 값을 가져오거나 저장할 크기도, 참조할 크기도 정해지지 않는다.

즉, void 포인터는 역참조가 불가능하다.

ptr = numptr;         // void 포인터에 int 포인터 저장
printf("%d", *ptr);   // void 포인터는 역참조할 수 없음. 컴파일 에러

ptr = cptr;           // void 포인터에 char 포인터 저장
printf("%c", *ptr);   // void 포인터는 역참조할 수 없음. 컴파일 에러

역참조를 할 때 접근해야할 메모리 크기가 정해지지 않았기 때문이다.

void 포인터의 사용 이유?

그럼 왜 역참조가 불가능한 void 포인터를 사용하는 것일까?

나도 잘 모르겠다

더 공부해야지 히히


상수(const)와 포인터

포인터에도 const 키워드를 사용할 수 있다.

하지만 const 키워드의 위치에 따라 의미가 달라진다.

먼저 상수를 가리키는 포인터이다.

const int num = 10; // int형 상수
const int *numptr;  // int형 상수를 가리키는 포인터.
                    // int const *numptr도 같은 의미임.(const의 위치만 다름) 
numptr = &num;
*numptr = 20;       // 컴파일 에러 발생. num1이 상수이므로 역참조로 값을 변경할 수 없음

 num이 const int형이므로 이 변수의 주소를 넣을 수 있는 포인터 역시 const int *로 선언해야 한다.

 num은 상수이므로 역참조하여 값을 조작하려고 시도한다면 컴파일 에러가 발생한다.

int num1 = 10;    // int형 변수
int num2 = 20;    // int형 변수
int * const numptr = &num1;    // int형 포인터 상수 선언 및 초기화

numptr = &num2;    // 컴파일 에러. 포인터(메모리 주소)를 변경할 수 없음

 numptr에 num1의 주솟값이 저장된 상태에서 num2의 주솟값으로 초기화를 시도한다면 컴파일 에러가 발생한다.

 numptr은 포인터 자체가 상수로 선언되었기 때문에 최초 초기화 값 이외에 다른 주솟값을 대상으로 초기화를 시도할 수 없다.

즉, 위 코드의 numptr(상수 포인터)은 최초 초기화값 이외의 메모리 주소로 변경할 수 없게된다는 것이다.

 

앞서 보여준 두 개의 코드에서 알 수 있었던 개념을 합친 코드는 다음과 같다.

const int num1 = 10;    // int형 상수
const int num2 = 20;    // int형 상수
const int * const numptr = &num1;    // int형 상수를 가리키는 포인터 상수
                                     // int const * const numPtr도 같음

*numptr = 30;      // 컴파일 에러. num1이 상수이므로 역참조로 값을 변경할 수 없음
numptr = &num2;    // 컴파일 에러. 포인터(메모리 주소)를 변경할 수 없음

 

마무리

그럼 간단하게 상수와 포인터를 사용한 예제를 해석해 보며 포스팅을 마치도록 하겠다.

  • const int num = 10;
    → 변수 num을 10으로 상수화하겠다.
  • const int * ptr1 = &val1;
    'const int 형' 포인터 ptr1에 val1(상수)의 주솟값으로 초기화
  • int * const ptr2 = &val2;
    'int 형' 상수 포인터 ptr2를 val2의 주솟값으로 초기화
  • const int * const ptr3 = &val3;
    'const int 형' 상수 포인터 ptr3에 val3(상수)의 주솟값으로 초기화

참고 및 출처

  • 코딩 도장