C언어에서 메모리 할당과 해제 시 발생하는 흔한 오류와 해결책

도입 문구


C언어에서 메모리 할당과 해제는 프로그램의 안정성과 성능에 큰 영향을 미칩니다. 하지만 잘못된 메모리 관리로 인해 다양한 오류가 발생할 수 있습니다. 본 기사에서는 C언어에서 발생할 수 있는 흔한 메모리 오류를 소개하고, 이를 해결하는 방법을 다룹니다.

메모리 할당 실패


메모리 할당 실패는 대부분 malloc(), calloc()과 같은 함수에서 발생합니다. 메모리가 부족하거나 요청한 메모리 공간이 시스템에서 할당되지 않으면 NULL을 반환하므로 이를 확인해야 합니다. 할당 실패를 예방하기 위해서는 충분한 메모리 공간을 확보할 수 있도록 프로그램 설계를 신중하게 해야 하며, 메모리 할당 후 항상 반환값을 확인하는 습관을 들여야 합니다.

할당 실패 예시 코드


“`c
int *arr = malloc(sizeof(int) * 100);
if (arr == NULL) {
printf(“메모리 할당 실패\n”);
return 1;
}

위 코드에서 malloc() 함수가 메모리를 할당할 수 없을 경우, NULL이 반환되며 이에 대한 처리가 필요합니다.
<h3>할당된 메모리 초과 접근</h3>  
할당된 메모리 범위를 벗어나서 접근하면 버퍼 오버플로우가 발생합니다. 이는 데이터가 인접 메모리 영역을 덮어쓰는 문제로 이어져 프로그램 충돌이나 예기치 않은 동작을 초래할 수 있습니다. 이러한 오류를 방지하려면 할당된 메모리의 크기와 경계를 철저히 관리해야 하며, 반복문 등을 사용할 때 배열의 인덱스를 정확히 체크해야 합니다.  

<h4>버퍼 오버플로우 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 5);
for (int i = 0; i < 6; i++) { // 배열 크기를 초과한 접근
arr[i] = i;
}

위 코드에서 배열의 크기는 5인데, 인덱스 6까지 접근하려 하므로 버퍼 오버플로우가 발생합니다. 이를 방지하려면 배열의 크기보다 큰 인덱스로 접근하지 않도록 주의해야 합니다.
<h3>메모리 누수</h3>  
메모리 누수는 할당된 메모리가 해제되지 않고 남아 있는 문제입니다. 이는 프로그램이 종료될 때까지 누적되며, 장기적으로 시스템 자원을 낭비하게 만듭니다. 메모리 누수를 방지하려면 동적으로 할당된 메모리는 사용 후 반드시 free()로 해제해야 하며, 이를 관리하는 체계적인 방법이 필요합니다.  

<h4>메모리 누수 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 10);
// 사용 후 메모리 해제하지 않음
// arr = NULL;

위 코드에서는 malloc()으로 메모리를 할당한 후 해제하지 않아서 메모리 누수가 발생합니다. 메모리를 더 이상 사용하지 않으면 반드시 free()로 해제하고, 포인터를 NULL로 설정하는 습관을 들여야 합니다.
<h3>이중 해제 오류</h3>  
동일한 메모리를 두 번 이상 해제하는 이중 해제 오류는 시스템의 불안정을 초래할 수 있습니다. 이는 이미 해제된 메모리를 다시 해제하려고 시도하는 경우 발생하며, 프로그램 충돌이나 예기치 않은 동작을 유발할 수 있습니다. 이중 해제를 방지하려면 메모리를 해제한 후 해당 포인터를 NULL로 설정하여 다시 해제되지 않도록 해야 합니다.  

<h4>이중 해제 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 10);
free(arr);
free(arr); // 이중 해제 발생

위 코드에서는 메모리를 두 번 해제하려고 하므로 이중 해제 오류가 발생합니다. 이를 방지하려면 메모리 해제 후 포인터를 NULL로 설정하여 다시 해제되지 않도록 해야 합니다.  

c
free(arr);
arr = NULL; // 이중 해제를 방지

<h3>잘못된 포인터 참조</h3>  
할당되지 않거나 이미 해제된 메모리를 참조하는 잘못된 포인터 참조는 프로그램을 충돌시키는 주된 원인 중 하나입니다. 이는 프로그램 실행 중 예기치 않은 오류를 발생시키며, 메모리의 예기치 않은 영역을 수정하거나 접근할 위험이 있습니다. 잘못된 포인터 참조를 방지하려면 포인터를 초기화하고, 메모리 해제 후 포인터를 NULL로 설정하는 습관을 들여야 합니다.  

<h4>잘못된 포인터 참조 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 10);
free(arr);
arr[0] = 5; // 이미 해제된 메모리 접근

위 코드에서는 메모리가 해제된 후 해당 메모리에 접근하려 하므로 잘못된 포인터 참조가 발생합니다. 해제 후 포인터를 NULL로 설정하여 이런 오류를 예방할 수 있습니다.  

c
free(arr);
arr = NULL; // 포인터를 NULL로 설정하여 잘못된 참조 방지

<h3>동적 배열 크기 변경 오류</h3>  
동적 배열의 크기를 변경할 때 발생하는 오류도 흔히 볼 수 있습니다. realloc()을 사용할 때, 새로운 메모리 공간을 확보하지 못하거나 이전 데이터가 손실되는 문제가 발생할 수 있습니다. realloc() 함수는 새로 할당된 메모리 공간에 기존 데이터를 복사하는데, 실패할 경우 기존 메모리 영역은 그대로 유지되므로 데이터 손실을 방지할 수 있습니다. 이를 예방하려면 realloc() 호출 후 반환값을 항상 확인하고, 메모리 재할당이 실패했을 때 적절한 처리를 해야 합니다.  

<h4>동적 배열 크기 변경 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 5);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;

int *new_arr = realloc(arr, sizeof(int) * 10);
if (new_arr == NULL) {
// realloc 실패 시 기존 배열 유지
printf(“메모리 재할당 실패\n”);
free(arr);
return 1;
}
arr = new_arr; // realloc 성공 시 새로운 메모리 할당

위 코드에서는 realloc()을 사용하여 배열 크기를 변경하고 있습니다. realloc()이 실패하면 기존 메모리 영역이 그대로 유지되고, 실패 시 적절히 처리해야 합니다.
<h3>메모리 경계 검사 미비</h3>  
메모리 할당 후 경계를 체크하지 않으면, 잘못된 접근이 발생할 수 있습니다. 이는 대부분 반복문에서 배열을 다룰 때, 경계를 벗어난 접근으로 이어져 문제를 일으킬 수 있습니다. 배열의 크기를 넘어서는 인덱스에 접근하거나, 할당되지 않은 메모리를 참조하면 프로그램이 비정상적으로 동작하거나 충돌을 일으킬 수 있습니다. 이를 방지하기 위해서는 배열 크기와 메모리 할당 크기를 정확히 계산하고, 반복문 내에서 인덱스를 엄격히 체크하는 것이 중요합니다.  

<h4>경계 검사 미비 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 5);
for (int i = 0; i < 6; i++) { // 배열 크기를 벗어난 인덱스 접근
arr[i] = i;
}

위 코드에서는 배열의 크기가 5인데, 인덱스 6까지 접근하려고 하므로 메모리 경계를 벗어난 접근이 발생합니다. 이를 방지하려면 반복문 내에서 인덱스를 배열 크기보다 작게 설정해야 합니다.  

c
for (int i = 0; i < 5; i++) { // 배열 크기 내에서 접근
arr[i] = i;
}

<h3>메모리 할당 함수 사용 시 주의사항</h3>  
malloc()free() 사용 시에는 반환값과 상태를 항상 점검하고, 예외 처리를 통해 안정성을 높여야 합니다. 메모리 할당 실패 시 적절한 오류 메시지를 출력하고, 문제가 발생한 위치를 정확하게 파악하는 것이 중요합니다. 또한, 할당된 메모리는 반드시 사용 후 해제해야 하며, free() 호출 후 포인터를 NULL로 설정하여 이중 해제나 잘못된 참조를 방지할 수 있습니다.  

<h4>메모리 할당 함수 사용 예시 코드</h4>  

c
int *arr = malloc(sizeof(int) * 10);
if (arr == NULL) {
printf(“메모리 할당 실패\n”);
return 1; // 할당 실패 시 적절한 처리
}

// 메모리 사용 후
free(arr);
arr = NULL; // 이중 해제 방지
“`
위 코드에서는 malloc()의 반환값을 확인하여 할당 실패 시 처리하고, free() 후 포인터를 NULL로 설정하여 안전하게 메모리를 관리합니다.

요약


C언어에서 메모리 오류는 프로그램의 안정성에 큰 영향을 미칩니다. 할당 실패, 메모리 초과 접근, 메모리 누수, 이중 해제, 잘못된 포인터 참조 등 다양한 오류가 발생할 수 있으며, 이를 예방하기 위해 철저한 메모리 관리가 필요합니다. malloc()과 free()의 사용 시 항상 반환값을 확인하고, 메모리 해제 후 포인터를 NULL로 설정하는 등의 안전한 메모리 관리 습관을 통해 오류를 방지할 수 있습니다.