void 포인터가 무엇인가?
void 포인터는 데이터 타입에 구애받지 않는, 메모리를 가리키는 포인터입니다.
포인터의 선언은 다음과 같습니다.
데이터_타입* 변수명
이 "데이터 타입"에 void가 쓰여서, 정해진 데이터 타입이 없다는 것을 나타냅니다.
char c;
int* pInt = &c; // compile error !!
void* pVoid = &c; // ok
위에서 char 포인터를 int 포인터에 대입하면 데이터 타입 불일치로 컴파일이 되지 않습니다.
그러나, void 포인터는 메모리 주소이기만 하면 전혀 데이터 타입을 고려하지 않습니다.
char*, 배열 포인터, 함수 포인터 등등 주소를 가리키는 것은 다 담을 수 있죠.
하지만, 단점도 있습니다.
우선, 역참조 연산자 *를 사용할 수 없습니다.
void 포인터가 가리키는 데이터 타입을 알 수 없기 때문이죠.
포인터 사용의 목적의 하나인 가리키는 데이터를 읽거나 사용을 할 수 없습니다.
또한, void 포인터를 대상으로 +, -, ++ 등의 연산을 수행하면 위와 마찬가지 이유로 컴파일 경고를 만나게 될 것입니다.
int a = 10;
void* p = &a;
p++; // compile warning !!
그래서, void 포인터가 가리키는 데이터를 사용하기 위해서는, 적절한 타입으로 형 변환을 해야 합니다.
int genericFunc( void* pVoid ){
int* pInt = (int*)pVoid; // 포인터 타입 변환이 필요합니다.
return *pInt;
}
int main(){
int a = 0;
void* pVoid = &a;
int ret = genericFunc(pVoid);
return ret;
}
void 포인터의 사용법
void 포인터는 어떤 데이터 타입의 포인터도 담을 수 있다는 강점이 있습니다.
그렇기 때문에 다양한 방식의 사용법이 있습니다.
메모리 주소 출력
void 포인터는 데이터 타입에 상관없이, 메모리 주소를 가리키기 때문에 cout을 통해 객체의 주소를 출력할 수 있습니다.
int add(int a, int b){
return a+b;
}
int main(){
int (*pFunc)(int, int) = add; // 함수 포인터
cout << "Funtion Pointer adress: " << pFunc << "\n";
cout << "void Pointer adress: " << (void*)pFunc; // void 포인터로 타입 변환
return 0;
}
함수명은 함수의 주소를 가리키고 있기 때문에, 함수의 주소가 출력될 것으로 예상되지만 1이 출력됩니다.
이것은 cout객체의 << 연산자를 overloading 할 때, 함수 포인터가 bool 타입으로 변경되기 때문입니다.
그렇지만, 함수 포인터를 void 포인터로 변경하면 이 문제를 해결할 수 있습니다.
메모리 동적 할당
malloc 함수는 메모리 할당하고 메모리의 주소를 void 포인터로 반환합니다.
또한, free 함수는 malloc 함수가 할당한 메모리를 해제합니다. 이때의 인자는 malloc 함수가 리턴한 void 포인터입니다.
#include <malloc.h>
void* malloc(size_t _Size);
void free(void *_Memory);
하지만, 할당한 메모리를 void 포인터를 통해서는 바로 사용할 수 없다는 점을 기억해야 합니다.
#define ITEM_NUM 100
int main(){
void* pVoid = malloc(sizeof(int) * ITEM_NUM);
int* pInt = static_cast<int*>(pVoid); // int 포인터로 타입 변경
for( int i = 0; i < ITEM_NUM; i++){
pInt[i] = i;
}
free(pVoid); // 메모리 해제
return 0;
}
콜백(Callback) 함수
함수의 인자로 void 포인터를 사용하면 다양한 데이터 타입에 반응할 수 있는 유연하고 일반적인 함수를 만들 수 있습니다.
#include <iostream>
using namespace std;
void PrintIntData(void* pData){
int* pInt = (int*)pData;
cout << *pInt;
}
void PrintDoubleData(void* pData){
double* pDouble = (double*)pData;
cout << *pDouble;
}
void PrintData( void* pData, void (*pCallback)(void*) ){
pCallback(pData);
cout << " was printed.\n"; // 공통으로 출력되어야 할 내용
}
int main(){
int a = 10;
double d = 3.1415;
PrintData(&a, PrintIntData); // 데이터 타입에 따라 다른 반응을 할 수 있습니다
PrintData(&d, PrintDoubleData);
return 0;
}
void 포인터를 사용함으로써 PrintData 함수에 수정을 가하지 않고도 다양한 타입을 처리할 수 있습니다. 또한 PrintData는 데이터 타입별 출력 내용에 관여할 필요 없이 공통적인 내용에만 집중할 수 있을 겁니다.
함수에 함수를 전달하는 방법인 함수 포인터에 관한 설명은 아래 링크에서 보실 수 있습니다.
'C, C++ > C++ 언어' 카테고리의 다른 글
[C++] 클래스의 초기화 리스트 (Initializer List)를 사용하는 이유 (0) | 2024.05.07 |
---|---|
[C++] const 포인터, const 멤버 함수 및 const 클래스 (0) | 2024.05.04 |
[C++] 함수 포인터 개념 및 사용하는 이유 (0) | 2024.04.30 |
[C++] 포인터로 배열에 접근하기 (1) | 2024.04.30 |
[C++] 포인터의 개념 및 사용하는 이유 (0) | 2024.04.29 |