본문 바로가기
SW/C,C++

[C/C++] 이중포인터와 void 포인터

by FastBench 2024. 4. 20.

이중포인터와 void 포인터

문자열배열은 이중포인터이다

아래와 같이

문자열 배열이 있고,

해당 문자열배열의 주소를 void 포인터에 초기화시켰다고 가정해보자.

    char* arr[] = {"banana", "apple"};
    void* vrr = arr;

 

여기서 다들 아는 내용을 정리해보자.

  1. 배열의 이름은 해당 배열의 첫 번째 요소의 주소를 나타내므로, arr&(arr[0])와 동일하다.
  2. arrchar* 타입의 요소를 저장하는 배열이기 때문에, 이를 char** 타입으로 볼 수 있다.
    즉,
    arrchar* 타입의 배열의 첫 번째 요소의 주소를 가리키는 포인터이다.
  3. 포인터 변수의 크기는 64비트 시스템 기준 8바이트이다. 메모리 주소를 모두 표현해야 하기 때문이다.
  4. arr 는 문자열포인터의 배열로, 각 리터럴들의 시작 주소가 담겨져 있다.

문자열 배열은 그 자체로 이중포인터인 것이다.

 

void 포인터와 arr 과의 관계는 다음과 같이 그려진다.

따라서 void 포인터를 통해 문자열을 출력하려면, char** 으로 캐스팅을 해주어야 한다.

vrr 은 '문자열을 가리키는 포인터' 를 가리키는 포인터 이기 때문이다.

    printf("*(char**)vrr    : %s\n", *(char**)vrr); //결과는 banana

여기서 산술연산도 추가해서 장난을 쳐보자.

 

*(char**)vrr 의 타입은 char* , 즉 '문자열' 을 의미하므로, 만약 +1을 해준다면, 
char 의 크기에 맞게 주소가 1바이트만 더해져 anana 가 출력된다.

따라서 (char**)vrr 자체, 즉 '문자열을 가리키는 포인터' 에 1을 더해줘야 '포인터의 크기에 해당하는 8바이트'가 더해져 정상적으로 arr[1] : apple 을 출력할 수 있다.

    printf("*(char**)vrr    : %s\n", *(char**)vrr);
    printf("*(char**)vrr+1  : %s\n", *(char**)vrr+1);
    printf("*((char**)vrr+1): %s\n", *((char**)vrr+1));

 

이중포인터 예시

아래는 char** 타입을 인자로 받아 문자열을 출력하는 예시이다.

#include <stdio.h>

void print_array(char** arr, int idx){
    printf("%s\n",arr[idx]);
}

int main(){
    char* arr[] = {"banana", "apple"};
    print_array(arr, 1);
    return 0;
}

apple 이 정상적으로 출력된다.

void print_array_void(void* arr, int idx){
    printf("%s\n", ((char**)arr)[idx]);
}

같은 주소를 가리키기만 한다면, void* 타입으로 배열을 받아도 문제 없다.

 

그런데 왜 도대체 이런 쓸대없는 짓을 해야하는 걸까?

그 이유는 void 포인터가 어떠한 데이터 타입의 포인터로도 캐스팅할 수 있는 범용성을 가지고 있기 때문이다.

따라서 , void 포인터는 다양한 함수와 라이브러리에서 타입에 독립적인 인터페이스를 제공할 때 핵심적으로 활용된다

void 포인터의 사용례 및 중요성

  1. 타입 독립성(Type Agnosticism):
    • void 포인터는 특정 데이터 타입을 명시하지 않기 때문에, 어떠한 타입의 데이터도 가리킬 수 있다. 이 특성 덕분에, 다양한 데이터 타입을 처리할 수 있는 범용 함수나 라이브러리를 설계할 때 유용하다. 예를 들어, 표준 라이브러리 함수인 qsort()는 void*를 사용하여 어떤 타입의 배열도 정렬할 수 있다.
      다음 게시글에서 이를 제대로 알아보자.
  2. 메모리 관리의 유연성:
    • void 포인터는 동적 메모리 할당과 관련된 함수들(malloc, free 등)에서 반환된 포인터 타입으로 사용된다. 이를 통해 할당된 메모리 블록을 원하는 타입의 데이터로 자유롭게 캐스팅하여 사용할 수 있다. 이는 프로그래머가 런타임에 다양한 데이터 타입의 메모리 요구사항을 동적으로 처리할 수 있게 해준다.
  3. 인터페이스 표준화:
    • 여러 종류의 데이터 타입을 처리해야 하는 라이브러리 및 API에서 void 포인터를 사용하면 함수 인터페이스를 단순화시킬 수 있다. 이는 프로그래밍의 복잡성을 줄이고, 코드의 재사용성을 높이는 데 기여한다.

다음 게시글에서 void 포인터를 사용하여, 다양한 타입을 처리하는 함수를 만들어보자.

댓글