Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C/C++에서 배열 범위를 벗어난 접근이 위험한 이유


Java와 같은 언어에서는 배열을 선언된 범위를 벗어나 접근할 경우 java.lang.ArrayIndexOutOfBoundsException과 같은 예외가 발생하여 오류를 즉시 알려줍니다. 그러나 C언어에는 이러한 안전장치가 없기 때문에, 배열을 범위 밖으로 접근하더라도 컴파일러가 이를 잡아주지 못하고 정의되지 않은 동작(undefined behavior)이 발생할 수 있습니다.

이는 프로그램이 비정상적으로 종료되거나, 엉뚱한 값이 출력되며, 심각한 경우 보안 취약점으로까지 이어질 수 있는 위험한 상황입니다. 아래의 C 프로그램 예제를 통해 이를 직접 확인해 보겠습니다.

예제

#include <stdio.h>
int main() {
    int arr[] = {1,2,3,4,5};
    printf("The elements of array : ");
    for(int i = 0; i<6; i++)
    printf(" %d",arr[i]);
    return 0;
}

출력 결과

위 프로그램을 실행하면 다음과 같은 출력 결과를 얻을 수 있습니다.

The elements of array : 1 2 3 4 5 32765

코드 분석

이제 위 프로그램이 어떻게 동작하는지 자세히 살펴보겠습니다.

배열 arr에는 인덱스 0부터 4까지 총 5개의 값만 할당되어 있습니다. 하지만 반복문은 인덱스 5까지 실행하도록 작성되었기 때문에, arr[5]에 접근하는 순간 실제로 값이 할당되지 않은 메모리 영역을 읽게 되고, 그 결과 예측할 수 없는 쓰레기 값(garbage value)인 32765가 출력됩니다. 이를 보여주는 핵심 코드 조각은 다음과 같습니다.

int arr[] = {1,2,3,4,5};
printf("The elements of array : ");
for(int i = 0; i<6; i++)
printf(" %d",arr[i]);

출력된 32765는 매번 동일하지 않으며, 실행 환경이나 컴파일러에 따라 달라질 수 있다는 점에 유의해야 합니다. 이것이 바로 '정의되지 않은 동작'의 전형적인 특징입니다.

범위 초과 접근 방지 방법

이러한 문제를 예방하기 위해 다음과 같은 방법들을 활용할 수 있습니다.

  • 반복 조건을 신중하게 작성: 반복문에서 인덱스가 배열 크기를 넘지 않도록 항상 확인합니다.
  • sizeof 연산자 활용: sizeof(arr)/sizeof(arr[0])처럼 배열 크기를 직접 계산하여 사용하면 인덱스 실수를 줄일 수 있습니다.
  • C++에서는 std::vector::at() 사용: C++에서는 std::vector의 at() 함수를 사용하면 범위를 벗어난 접근 시 std::out_of_range 예외가 발생하여 안전하게 처리할 수 있습니다.
  • 정적 분석 도구 및 Sanitizer 활용: AddressSanitizer(ASan)와 같은 도구를 사용하면 런타임에 배열 범위 초과 접근을 탐지할 수 있습니다.

C와 C++는 개발자에게 메모리 관리의 강력한 자유를 제공하는 대신, 그만큼 신중함을 요구하는 언어입니다. 배열 경계 검사를 습관화하면 디버깅하기 어려운 버그와 보안 취약점을 사전에 예방할 수 있습니다.