포인터와 주소 제어
포인터(Pointer)
포인터는 특정 타입의 메모리 주소를 저장한다.
int a = 10;
int* p = &a; // p는 int형 변수를 가리키는 포인터
p
자체는 메모리 주소를 저장하고,*p
를 통해p
가 가리키는 주소에 저장된 값을 가져올 수 있다. (역참조)
printf("%d", *p); // 10
*
은 선언할 때는 "포인터 타입 선언"을 의미
사용할 때는 "역참조 연산자"로 가리키는 값을 읽는다는 의미
포인터 타입
포인터도 타입이 명확히 존재하며, 어떤 타입의 데이터를 가리키는지 지정해야 함.
포인터의 타입은 역참조 시 메모리를 해석하는 방법을 결정한다.
char c = 'A';
char* pc = &c; // char형을 가리키는 포인터
float f = 3.14f;
float* pf = &f; // float형을 가리키는 포인터
포인터 연산자
-
주소 연산자
&
- 변수의 메모리 주소에 접근
int a = 5;
int* p = &a; // a의 주소를 p에 저장 -
역참조 연산자
*
- 포인터 변수가 가리키는 메모리의 값에 접근
*p = 20; // a의 값을 20으로 변경
printf("%d", a); // 20 -
포인터 산술 연산
- 포인터끼리
+
,-
연산 가능 - 연산 시, 포인터 타입 크기만큼 이동
int arr[3] = {1, 2, 3};
int *p = arr;
printf("%d", *(p + 1)); // 2p + 1
은int
크기(4바이트)만큼 다음 메모리 주소로 이동p++
,p--
도 가능(배열 순회 시 유용함) - 포인터끼리
동적 할당 및 메모리 해제
포인터를 활용하여 프로그램 런타임 중 메모리를 동적으로 할당하고 해제할 수 있다.
-
malloc
(Memory Allocation)- 고정 크기의 메모리 할당
- 할당 후 초기화 필요, 쓰래기 값이 포함됨
int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
*p = 10; // 초기화 필요 -
calloc
(Contiguous Allocation)- 연속된 메모리 할당
- 모든 블록을 0으로 초기화
동적 배열을 생성하는 경우
calloc
을 사용하여 모든 요소를 0으로 초기화int* arr = (int*)calloc(4, sizeof(int)); // int 4개 크기만큼 메모리 할당 및 초기화
-
realloc
(Reallocation)- 기존 할당된 메모리 크기 변경
- 기존 데이터를 유지(크기가 줄어들어 잘린 경우는 X)
int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
p = (int*)realloc(p, sizeof(int) * 8); // 크기 확장 -
free
- 동적으로 할당된 메모리를 해제하여 운영체제에 반환
- 더이상 사용하지 않는 메모리를 **해제하지 않으면 메모리 누수(Memory Leak)**가 발생
int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
free(p); // 해제
메모리 직접 제어 시 주의사항
잘못된 타입 해석
- 메모리 주소는 할당 받은 메모리 공간의 시작 주소이다.
- 이 숫자를 어떻게 해석할지는 타입에 달려 있음.
int a = 65;
char* p = (char*)&a;
printf("%c\n", *p); // 'A' (ASCII 65)
&a
는 변수 a의 시작 주소이다.char
로 읽으면 1바이트,int
로 읽으면 4바이트로 해석
포인터 초기화
- 포인터를 초기화하지 않고 사용하면, 쓰래기 값(garbage value) 을 가져올 수 있다.
int* p; // 초기화 안됨
*p = 10; // 어느 주소를 가리키는지 모른다.(정의되지 않은 동작, Undefined Behavior)
NULL 포인터 확인
- 포인터가 NULL이면 아무것도 가리키지 않는 상태
- NULL 포인터를 역참조하면 프로그램이 즉시 크래시(튕김)된다.
int* p = NULL;
*p = 10; // 잘못된 메모리 접근(Segmentation Fault)
역참조 전에 반드시 NULL 여부 체크
if (p != NULL) {
*p = 10;
}
해제된 메모리 접근(Dangling Pointer)
- 해제된 포인터를 사용하면 정의되지 않은 동작(Undefined Behavior) 발생
int *p = (int*)malloc(sizeof(int)); // 동적 할당
*p = 5;
free(p); // 해제
*p = 10; // Undefined Behavior
메모리를 해제한 후에는 반드시 NULL로 설정
free(p);
p = NULL;
중복 해제(double free)
- 포인터를 중복해서 해제하면 정의되지 않은 동작(Undefined Behavior) 발생
int *p = (int*)malloc(sizeof(int)); // 동적 할당
free(p); // 해제
free(p); // 중복 해제, double free detected in tcache 2
메모리 해제 후 NULL로 설정하여 이중 해제를 막는다.