1. 문제 개요
C 언어에서 배열을 사용할 때, 경계 밖(out-of-bounds)으로 데이터를 읽거나 쓰는 실수는 프로그램에 치명적인 오류를 유발할 수 있습니다. 이 글에서는 배열 경계 밖 접근의 위험성을 살펴보고, 프로그램과 시스템을 안전하게 보호하는 방법을 제시합니다.
2. 배열 경계 밖 접근의 위험성
2.1 ISO C 표준의 "정의되지 않은 동작"
ISO C 표준에서는 배열 경계 밖 접근을 "정의되지 않은 동작(undefined behavior)"으로 간주합니다. 이는 컴파일러나 실행 환경에 따라 다음과 같은 예측할 수 없는 결과를 초래할 수 있습니다:
- 프로그램이 계속 실행되지만 잘못된 값을 반환.
- 세그멘테이션 오류(segmentation fault)로 프로그램이 비정상 종료.
- 메모리 오염으로 다른 코드에 영향을 미침.
2.2 메모리 보호 여부에 따른 차이
운영 체제(OS) 보호
현대 운영 체제는 프로그램의 메모리를 독립적으로 관리하여, 다른 프로세스의 메모리를 보호합니다. 따라서 배열 경계를 초과해도 대부분의 경우 프로그램 외부에 영향을 주지 않습니다. 하지만:
- 버퍼 오버런: 악의적인 코드가 메모리의 민감한 부분을 조작하는 데 악용될 수 있습니다.
- 자원 고갈: 프로그램이 과도한 CPU나 메모리를 소비하여 시스템 전체에 영향을 미칠 수 있습니다.
메모리 보호 없는 시스템
임베디드 시스템이나 메모리 보호가 없는 환경에서는 배열 경계 밖 접근이 더욱 위험합니다. 예를 들어:
- 하드웨어 레지스터를 잘못된 값으로 설정하여 하드웨어 손상 발생.
- 주변 기기의 오작동 유발.
3. 구체적인 사례
3.1 배열 읽기 초과
int arr[3] = {1, 2, 3};
printf("%d\n", arr[5]); // 정의되지 않은 동작
- 위험성: 프로그램이 잘못된 메모리를 읽어 예상치 못한 값을 반환할 수 있습니다.
3.2 배열 쓰기 초과
int arr[3] = {1, 2, 3};
arr[5] = 10; // 정의되지 않은 동작
- 위험성: 메모리를 덮어쓰면서 중요한 데이터를 손상시키거나 다른 코드에 영향을 줄 수 있습니다.
4. 안전한 코딩을 위한 팁
4.1 배열 경계 확인
항상 배열의 경계를 확인하여 초과 접근을 방지합니다:
int arr[3] = {1, 2, 3};
for (int i = 0; i < 3; i++) {
printf("%d\n", arr[i]);
}
4.2 동적 메모리 사용 시 크기 관리
동적 메모리를 사용하는 경우, 크기를 명시적으로 추적해야 합니다:
int *arr = malloc(3 * sizeof(int));
if (arr) {
for (int i = 0; i < 3; i++) arr[i] = i;
free(arr);
}
4.3 디버깅 도구 활용
디버깅 도구를 사용하여 배열 초과 접근을 탐지할 수 있습니다:
- Valgrind: 메모리 오류를 탐지하는 강력한 도구.
- AddressSanitizer: 배열 초과, 메모리 오염 등 다양한 메모리 문제를 탐지.
5. 결론
C에서 배열 경계 밖 접근은 심각한 프로그램 오류와 보안 문제를 유발할 수 있습니다. 현대 운영 체제가 많은 경우 이를 보호하지만, 완전한 안전을 보장하지는 못합니다. 배열 크기를 명확히 추적하고 디버깅 도구를 사용하여 안전한 코드를 작성하는 것이 중요합니다. 이러한 방식을 통해 배열 사용의 위험을 최소화할 수 있습니다.