C++에서 지역(로컬) 배열은 함수에서 직접 반환할 수 없습니다. 그 이유는 지역 배열이 스택 메모리에 생성되며, 함수 호출이 종료되는 순간 해당 메모리가 해제되어 더 이상 유효하지 않게 되기 때문입니다. 이미 사라진 배열의 주소를 반환하면 미정의 동작(undefined behavior)이 발생할 수 있습니다.
이 문제를 해결하는 대표적인 방법 중 하나는 함수 내부에 정적(static) 배열을 선언하는 것입니다. 정적 배열의 수명(lifetime)은 프로그램 실행 전체와 동일하기 때문에, 함수가 종료된 후에도 메모리에 유지되므로 문제없이 반환할 수 있습니다.
다음 예제 프로그램을 통해 이를 확인해 보겠습니다.
예제 코드
#include <iostream>
using namespace std;
int *retArray() {
static int arr[10];
for(int i = 0; i<10; i++) {
arr[i] = i+1;
}
return arr;
}
int main() {
int *ptr = retArray();
cout <<"The array elements are: ";
for(int i = 0; i<10; i++) {
cout<< ptr[i] <<" ";
}
return 0;
}실행 결과
The array elements are: 1 2 3 4 5 6 7 8 9 10
코드 설명
먼저 retArray() 함수 내부를 살펴보겠습니다. 이 함수에서는 정적 배열 arr을 선언한 뒤, for 반복문을 사용해 각 요소를 초기화합니다. 마지막으로 배열 arr을 반환합니다. 해당 코드는 다음과 같습니다.
int *retArray() {
static int arr[10];
for(int i = 0; i<10; i++) {
arr[i] = i+1;
}
return arr;
}여기서 핵심은 static 키워드입니다. 일반 지역 배열이라면 함수가 끝나는 순간 소멸되지만, 정적 배열은 데이터 영역에 할당되어 프로그램이 종료될 때까지 유지됩니다. 따라서 함수가 반환된 이후에도 포인터를 통해 안전하게 접근할 수 있습니다.
다음으로 main() 함수를 살펴보겠습니다. main()에서는 retArray()를 호출하고, 반환된 주소를 포인터 ptr에 저장합니다. 이 포인터는 배열 arr의 시작 위치를 가리키며, for 반복문을 통해 배열의 모든 요소를 출력합니다.
int main() {
int *ptr = retArray();
cout <<"The array elements are: ";
for(int i = 0; i<10; i++) {
cout<< ptr[i] <<" ";
}
return 0;
}주의할 점과 대안
정적 배열을 사용하는 방법은 간단하지만 몇 가지 단점이 있습니다.
- 상태 공유 문제: 정적 변수는 프로그램 전체에서 하나만 존재하므로, 함수를 여러 번 호출하면 이전 호출의 값이 남아 있거나 덮어써집니다.
- 스레드 안전성: 멀티스레드 환경에서 여러 스레드가 동시에 같은 정적 배열에 접근하면 경합 조건(race condition)이 발생할 수 있습니다.
따라서 실무에서는 상황에 따라 다음과 같은 대안을 고려하는 것이 좋습니다.
- 동적 할당:
new로 힙 메모리에 배열을 생성하고 반환한 뒤, 사용이 끝나면delete[]로 해제합니다. - std::vector / std::array: C++ 표준 라이브러리 컨테이너를 값으로 반환하면 자동으로 복사 또는 이동되므로 가장 안전하고 현대적인 방법입니다.
- 호출자가 버퍼 전달: 함수 매개변수로 배열(포인터와 크기)을 받아 결과를 채우는 방식도 널리 사용됩니다.
결론적으로, C++ 함수에서 배열을 반환해야 한다면 정적 배열이 빠르고 간편한 해결책이 될 수 있지만, 재사용성과 안전성을 고려하면 std::vector 같은 컨테이너를 사용하는 것이 바람직합니다.