Quick Syntax
이중 포인터는 대상 값이 아니라 호출자의 포인터 변수 자체를 다른 주소로 바꿔야 할 때 사용합니다. 출력 매개변수(out parameter)로 쓴다면 성공·실패 뒤 포인터가 어떤 상태인지 먼저 정해야 합니다.
int value = 10;
int *p = &value;
int pp = &p;
*pp = NULL; /* p 자체를 바꿈 */
p = &value;
pp = 99; /* value를 바꿈 */
int *result = NULL;
if (make_value(&result)) {
/* make_value가 result의 새 소유권을 넘긴 경우 사용한다. */
free(result);
}T *는 *p가 가리키는 값을 바꾸고, T **는 p 자체를 바꿉니다. out parameter는 실패할 때 기존 포인터를 유지하는지 NULL로 만드는지까지 호출자와 약속해야 합니다.
읽는 법
T *p는 T를 가리키고, T **pp는 T를 가리키는 포인터 변수를 가리킵니다.
*pp는 호출자의 포인터 변수입니다.**pp는 그 포인터가 가리키는 원본 값입니다.- head 포인터가 바뀔 수 있는 연결 리스트 삭제, 새 allocation을 호출자에게 전달하는 API에서 자연스럽게 나옵니다.
argv가 char **인 이유도 같습니다. 문자열을 가리키는 char *들을 다시 가리키는 포인터이므로 argv[i]에서 i번째 문자열 포인터를 얻습니다.
out parameter
새 allocation을 만드는 함수는 실패했을 때 기존 포인터를 건드리지 않는 편이 안전합니다. 아래 함수는 새 allocation 전용이므로, 호출자는 out이 가리키는 포인터를 NULL로 시작해야 합니다.
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
bool allocate_ints(size_t count, int out) {
if (out == NULL || *out != NULL ||
count == 0 || count > SIZE_MAX / sizeof(out)) {
return false;
}
int *data = malloc(count * sizeof(*data));
if (data == NULL) {
return false;
}
*out = data;
return true;
}
int *data = NULL;
if (allocate_ints(4, &data)) {
free(data);
}out에 바로 malloc 결과를 넣으면, 기존 allocation을 받는 일반 함수에서는 실패 시 소유권을 잃을 수 있습니다. 임시 포인터에 성공을 확인한 뒤에만 *out을 바꾸는 방식이 기본입니다.
주의할 점
T **는 포인터 깊이 하나가 늘어난 별도 타입입니다. 타입이 맞지 않는 포인터를 캐스팅해 억지로 넘기지 않습니다.out == NULL과*out == NULL은 다른 검사입니다. 전자는 포인터 변수의 주소가 없는 경우이고, 후자는 호출자 포인터가 아직 대상을 가리키지 않는 경우입니다.- 새 allocation 함수인지, 기존 allocation을 교체하는 함수인지, 실패 시 기존 값을 유지하는지를 함수 이름과 반환값으로 분명히 합니다.
char **, 연결 리스트 head, 동적 배열 재할당처럼 포인터 변수가 바뀌는 경우에만 사용합니다.
참고 링크
2 sources