프로그래밍을 하다 보면 배열(array)의 특성을 이해하는 것이 중요합니다.
특히, C#을 포함한 대부분의 언어에서 배열은 참조 타입(reference type) 으로 동작한다는 점을 정확히 알고 있어야 합니다.
이번 글에서는 배열이 참조 타입이라는 것이 무엇을 의미하는지, 그리고 이를 어떻게 활용해야 하는지 알아보겠습니다.
값 타입 vs 참조 타입
C# 에서 데이터 타입은 크게 값 타입(value type) 과 참조 타입(reference type) 으로 나뉩니다.
* 값 타입
- 변수를 할당하면 해당 값을 직접 저장합니다.
- 예: int, float, bool, struct 등
* 참조 타입
- 변수를 할당하면 값이 아니라 메모리 주소(참조값) 을 저장합니다.
- 예: class, string, array, delegate 등
배열은 class 와 마찬가지로 참조 타입 이기 때문에, 변수를 다른 변수에 할당할 때 배열 전체가 복사되는 것이 아니라 배열의 참조값(주소) 만 복사됩니다.
배열이 참조 타입임을 확인하는 예제
아래 예제를 통해 배열이 참조 타입임을 직접 확인해 봅시다.
using System;
class Program
{
static void Main()
{
int[] array1 = { 1, 2, 3 };
// array1의 참조값(주소)이 array2에 복사됨
int[] array2 = array1;
array2[0] = 100; // array2의 값을 변경
Console.WriteLine(array1[0]); // 100이 출력됨
}
}
결과
100
위 코드에서 array1을 array2에 할당했을 때, 배열이 복사된 것이 아니라 참조값(주소)만 복사 되었습니다.
따라서 array2[0]을 변경하면, array1[0]도 변경된 것을 볼 수 있습니다.
배열이 참조 타입임을 고려해야 하는 상황
배열이 참조 타입 이기 때문에, 실수로 원본 배열을 변경하는 경우가 종종 발생합니다.
이를 방지하기 위해 다음 방법을 사용할 수 있습니다.
원본 보호를 위한 readonly 사용
배열이 참조 타입 이기 때문에, 실수로 원본 배열을 변경하는 경우가 종종 발생합니다.
배열을 변경하지 못하도록 하고 싶다면, readonly 키워드를 사용하여 참조 자체를 변경할 수 없도록 할 수 있습니다.
readonly 로 선언하면, 생성자나 변수 선언 시에만 값을 설정할 수 있습니다.
( const 는 선언 시에만 할당 가능 )
class Example
{
private readonly int[] numbers = { 1, 2, 3 };
public void ModifyArray()
{
// numbers = new int[3]; // 오류! 재할당 불가
numbers[0] = 100; // 개별 요소 변경은 가능
}
}
readonly 는 배열 자체의 참조값 변경을 막을 뿐, 내부 요소 변경은 허용 합니다.
배열을 함수에 전달할 때의 동작
배열은 참조 타입 이므로, 함수의 매개변수로 전달할 때도 참조값만 전달 됩니다.
따라서 함수 내에서 배열 요소를 변경하면 원본 배열도 변경 됩니다.
void ModifyArray(int[] arr)
{
arr[0] = 100;
}
int[] numbers = { 1, 2, 3 };
ModifyArray(numbers);
Console.WriteLine(numbers[0]); // 100 출력 (원본 배열 변경됨)
함수에 전달된 배열을 수정하면 원본 배열도 변경 됩니다.
반대로, 배열 자체를 새롭게 할당하는 경우 원본 배열에는 영향을 미치지 않습니다.
void ReassignArray(int[] arr)
{
// 새로운 배열을 할당 (원본에는 영향 없음)
arr = new int[] { 10, 20, 30 };
}
int[] numbers = { 1, 2, 3 };
ReassignArray(numbers);
Console.WriteLine(numbers[0]); // 1 (원본 배열은 그대로 유지됨)
함수 내부에서 배열을 새롭게 할당 해도 원본 배열에는 영향을 주지 않습니다.
arr 라는 지역 변수가 생성되고 arr 에 new 로 메모리를 할당하였으나,
함수를 빠져나오면서 arr 는 사라집니다. 그리고 이후에 GC(Garbage Collector) 에 의해 처리됩니다.
따라서 numbers 에는 아무 영향을 주지 않게 됩니다.
배열을 반환할 때 깊은 복사 vs 얕은 복사
배열을 반환할 때도 얕은 복사 가 이루어지므로, 원본 배열이 변경될 수 있습니다.
public int[] GetArray(int[] array)
{
int[] tempArray = array;
return tempArray;
}
위와 같은 방식으로 배열을 반환하면 참조값만 전달되므로 원본 배열과 연결된 상태가 유지됩니다.
new 연산자 사용 여부
일반적으로 new 연산자를 사용하면 heap 메모리에 공간이 생성되고, 참조 타입으로 동작한다고 이해하고 있습니다.
int [] numbers = { 1, 2, 3, 4, 5 };
비록 위 예시에서는 new 연산자를 명시적으로 사용하지 않았지만, 배열이므로 실제 데이타가 heap 메모리에 생성되고 numbers 변수는 그 배열의 주소를 stack 에 저장하게 되는 것입니다.
내부적으로는 다음과 같이 처리 됩니다.
int [] numbers = new int [ ] { 1, 2, 3, 4, 5 }
마무리
배열은 참조 타입 이므로 변수를 할당하면 배열 자체가 아니라 메모리 주소(참조값) 가 복사됩니다. 이 경우 별도의 복사본이 생성되지 않기 때문에
* 함수에 매개변수를 전달할 때
* return 으로 반환할 때
* 대입 연산자로 변수에 대입할 때
모두 주소만 전달되므로 이러한 동작을 수행할 때 깊은 복사가 일어나지 않으므로 수행 속도에 부담이 없습니다. 배열이 참조 타입이라는 개념을 이해했다면, 이를 활용하여 보다 안전하고 효율적인 코드를 작성할 수 있을 것입니다.
'C#' 카테고리의 다른 글
1. yield return 에 대해 (개념) (0) | 2025.03.09 |
---|---|
Action 과 Func 의 형변환 살펴보기 (0) | 2025.03.08 |
=> (람다 연산자) 이해하기 (0) | 2025.03.08 |
제네릭 (Generic) <T> 에 대해서 (0) | 2025.03.05 |
C# 에서 ? (nullable) 의 의미와 활용 방법 (0) | 2025.03.05 |