T, T*은 T의 포인터의 표현이다. 즉, T*의 변수는 T 객체의 주소를 가질 수 있다.
char c = 'a';
char∗ p = &c; // p holds the address of c; & is the address-of operato
포인터에 대한 기본 연산은 역참조, 즉 가리키는 객체를 참조하는 것이다. 이 작업을 간접 참조라고도 한다. 역참조 연산자는 접두사로 '*'을 사용한다.
char c = 'a';
char∗ p = &c; // p holds the address of c; & is the address-of operator
char c2 = ∗p; // c2 == ’a’; * is the dereference operator
p가 가리키는 객체는 c이고, c에 저장된 값은 'a'이므로 *p의 값은 'a'다. c2에 할당되는 값 또한 *p의 값인 'a'다. 포인터의 구현은 주소 지정 메커니즘에 직접 매핑하기 위한 것이다. 대부분의 시스템은 바이트 주소를 지정할 수 있다. word로부터 byte를 추출하는 하드웨어는 없는 편이고 한편으로 개별 bit를 직접 처리할 수 있는 기계 또한 거의 없다. 결과적으로 built-in 포인터 유형을 사용하여 독립적으로 할당되고 가리킬 수 있는 가장 작은 객체는 char다. bool은 최소한 char만큼의 공간을 차지한다.
'*'은 '~에 대한 포인터'라는 의미다. *는 유형 이름의 접미사로도 사용된다. 배열에 대한 포인터와 함수에 대한 포인터에는 더 복잡한 표기법이 필요하다.
int∗ pi; // pointer to int
char∗∗ ppc; // pointer to pointer to char
int∗ ap[15]; // array of 15 pointers to ints
int (∗fp)(char∗); // pointer to function taking a char* argument; returns an int
int∗ f(char∗); // function taking a char* argument; returns a pointer to int
함수에 대한 포인터는 유용할 수 있다.
void*
저수준 코드에서는 때대로 메모리 위치의 주소를 저장하거나 전달해야 한다. 실제로 어떤 유형의 개체가 거기에 저장되어 있는지는 알지 못한다. 이를 위해서 사용되는 것이 void*다. void*은 '알 수 없는 유형의 객체에 대한 포인터'라고 해석할 수 있다. 그러나 함수에 대한 포인터 또는 멤버에 대한 포인터에 대해서는 사용할 수 없다. 또한 void*은 다른 void*에 에 할당될 수 있고, void*은 등식과 부등식에 대해 비교할 수 있으며, void*는 명시적으로 다른 유형으로 변환될 수 있다.
컴파일러가 실제로 가리키는 개체의 종류를 알 수 없기 때문에 다른 작업은 안전하지 않다. 결과적으로 다른 작업을 수행하면 compile-time error가 발생한다. void*를 사용하려면 명시적으로 특정 유형에 대한 포인터로 변환해야 한다.
void f(int∗ pi)
{
void∗ pv = pi; // ok: implicit conversion of int* to void*
∗pv; // error : can’t dereference void*
++pv; // error : can’t increment void* (the size of the object pointed to is unknown)
int∗ pi2 = static_cast<int∗>(pv); // explicit conversion back to int*
double∗ pd1 = pv; // error
double∗ pd2 = pi; // error
double∗ pd3 = static_cast<double∗>(pv); // unsafe (§11.5.2)
}
일반적으로 포인터가 가리키는 객체의 유형과 다른 유형으로 변환(캐스팅)된 포인터를 사용하는 것은 안전하지 않다. 예를 들어 machine은 모든 double이 8byte로 할당된다고 가정할 수 있다. 그렇다면 pi가 그렇게 할당되지 않은 int를 가리키면 이상한 동작이 발생할 수 있다. 이러한 형식의 명시적 형식 변환은 본질적으로 안전하지 않고 보기에도 좋지 않다. 결과적으로 static_casting은 보기 좋지 않으며 코드에서 찾기 쉽도록 설계되었다.
void*의 주요 용도는 객체의 유형에 대한 가정을 허용하지 않는 함수에 대한 포인터를 전달하고 함수에서 유형이 지정되지 않은 객체를 반환하는 것이다. 이러한 객체를 사용하려면 명시적 유형 반환을 사용해야 한다.
void* 포인터를 사용하는 함수는 일반적으로 실제 하드웨어 리소스가 조작되는 시스템의 가장 낮은 수준에 존재한다.
void∗ my_alloc(siz e_t n); // allocate n bytes from my special hea
시스템의 더 높은 수준에서 void*의 발생은 설계 오류의 지표일 가능성이 있기 때문에 큰 의심을 가지고 봐야 한다. 최적화에 사용되는 경우 void*는 type-safe 인터페이스 뒤에 숨길 수 있다. 함수에 대한 포인터 및 멤버에 대한 포인터는 void*에 할당할 수 없다.
nullptr
리터럴 nullptr은 null 포인터, 즉 객체를 가리키지 않는 포인터를 나타낸다. 모든 포인터 유형에 할당할 수 있지만 다른 built-in 유형에는 사용할 수 없다.
int∗ pi = nullptr;
double∗ pd = nullptr;
int i = nullptr; // error : i is not a pointe
각 포인터 유형에 대한 null 포인터가 아니라 모든 포인터 유형에 사용할 수 있는 하나의 nullptr만 있다. nullptr이 도입되기 전에는 null 포인터 표기법으로 영(0)이 사용되었다.
int∗ x = 0; // x gets the value nullptr
주소가 0으로 할당되는 객체는 없고 0(all-zeros bit pattern)은 nullptr의 가장 일반적인 표현이다. 영(0)은 정수다. 그러나 표준 변환에서는 0을 포인터 또는 포인터-멤버 유형의 상수로 사용할 수 있다. null 포인터를 나타내기 위해 매크로 NULL을 정의하는 것이 일반적이었다.
int∗ p = NULL; // using the macro NULL
그러나 구현에 따라 NULL 정의에 차이가 있다. 예를 들자면, NULL은 0 또는 0L일 수 있다. C에서 NULL은 일반적으로 (void*) 0 이므로 C++에서는 규칙 위반이다.
int∗ p = NULL; // error : can’t assign a void* to an int
nullptr을 사용하면 코드를 대안보다 더 읽기 쉽게 만들고 함수가 포인터나 정수를 허용하도록 오버 로드될 때 잠재적인 혼동을 피할 수 있다.
'Program Language > C & C++' 카테고리의 다른 글
[C++] pointer & 소유권 (0) | 2021.12.29 |
---|---|
[C++] Pointer & const (0) | 2021.12.29 |
[C++] 별칭 (0) | 2021.12.16 |
[C++] 객체와 값 (0) | 2021.12.16 |
[C++] decltype 지정자 (0) | 2021.12.16 |