프로그래밍 개발 공부

[개발 실무] 변수는 값을 담는 상자가 아닙니다: 참조와 메모리로 다시 배우는 코딩 기초

wikys 2026. 2. 20. 12:40
혹시 리스트나 객체를 다른 변수에 할당했다가, 원본 데이터까지 엉뚱하게 바뀌어버린 버그를 경험한 적이 있으신가요?
let a = [1-3];
let b = a;
b.push(4);

console.log(a); // 결과는? [1-4] 
// "어라? 난 b만 바꿨는데 왜 a가 바뀌지?"
우리는 프로그래밍을 처음 배울 때 "변수(Variable)는 값을 저장하는 상자"라고 배웁니다. 하지만 이 설명은 '반만 맞는' 이야기입니다. 이 '상자 모델'만 믿고 코딩을 하다가는 위와 같은 참조(Reference) 관련 버그 앞에서 속수무책이 될 수밖에 없습니다.
오늘은 주니어 개발자가 가장 많이 오해하는 변수, 참조, 그리고 메모리의 진짜 관계를 파헤쳐 보겠습니다. 이 글을 다 읽고 나면, 더 이상 알 수 없는 데이터 변경 버그로 고생하지 않게 될 것입니다.
--------------------------------------------------------------------------------
1. 변수의 진짜 정체: 상자가 아니라 '이름표'
우리가 흔히 듣는 "변수는 물건(값)을 담는 상자"라는 비유는 직관적이지만, 객체(Object)나 배열(Array) 같은 복잡한 데이터를 다룰 때 오류를 범하게 만듭니다.
 
메모리 관점에서 변수의 진짜 정의는 다음과 같습니다.
    "변수는 값이 위치한 메모리 주소를 가리키는 '이름표(Label)'입니다."
 
이미지로 상상해보기
기존의 오해 (상자 모델): a라는 상자 안에 이라는 리스트가 꽉 차게 들어있다.
실제 작동 (이름표 모델): 이라는 리스트는 저 멀리 메모리 창고(Heap) 어딘가에 있고, 변수 a는 그 창고의 위치(주소, 예: 0x1234)가 적힌 포스트잇(이름표)일 뿐입니다.
 
즉, let b = a라는 코드는 상자 안의 물건을 복사해서 새 상자에 넣는 것이 아니라, a가 보고 있는 주소가 적힌 포스트잇을 하나 더 복사해서 b에게 주는 것과 같습니다. 결국 ab는 같은 대상(객체)을 가리키게 되는 것이죠.
--------------------------------------------------------------------------------
2. 메모리: 대형 물류 창고와 선반 번호
컴퓨터의 메모리는 거대한 물류 창고와 같습니다.
1. 메모리(RAM): 거대한 물류 창고 바닥입니다.
2. 주소(Address): 물건이 놓인 선반 번호(예: 1001번, 2004번)입니다.
3. 값(Value): 선반에 실제 놓인 물건입니다.
4. 변수(Variable): "1001번 선반을 보라"고 적힌 작업 지시서입니다.
Stack vs Heap: 물건의 크기에 따른 저장 방식
웹 자료에 따르면 현대 언어(Java, JS, Python 등)는 메모리를 크게 두 영역으로 나누어 관리합니다.
스택(Stack): 정해진 크기의 작고 간단한 데이터(정수, 불리언 등 기본형)를 저장합니다. 접근 속도가 매우 빠릅니다. 이곳에는 값 자체가 저장될 수 있습니다.
힙(Heap): 크기가 수시로 변할 수 있는 복잡한 데이터(객체, 배열, 클래스 인스턴스)를 저장합니다. 이곳은 정리되지 않은 자유 공간이므로, 반드시 주소(참조)를 통해 찾아가야 합니다.
우리가 변수에 객체를 담을 때, 실제 객체는 에 저장되고, 스택에 있는 변수에는 힙의 주소만 저장됩니다.
--------------------------------------------------------------------------------
3. 기본형(Primitive) vs 참조형(Object)
왜 숫자는 복사해도 서로 영향을 안 주는데, 리스트는 영향을 줄까요?
 
기본형 (Primitive Type)
숫자나 문자 같은 기본형은 불변(Immutable)입니다. a = 10에서 a = 20으로 바꾸면, 10을 20으로 고치는 게 아니라 새로운 메모리 공간에 20을 만들고 변수가 가리키는 방향을 바꿉니다. 즉, 주소가 달라집니다.
 
참조형 (Reference Type)
리스트나 객체는 가변(Mutable)입니다.
집 주소철수영희가 동시에 알고 있다고 가정해 봅시다. (ab가 같은 주소 참조)
철수가 그 집에 들어가서 벽지를 뜯어내면(내부 데이터 변경), 나중에 영희가 들어왔을 때 뜯겨진 벽지를 보게 됩니다.
이것이 바로 Aliasing(별칭) 버그의 원인입니다.
 
핵심: 변수 간 대입 연산자(=)를 사용할 때, 객체는 값이 복사되는 것이 아니라 '참조(위치)'가 연결되는 것입니다. 이를 막으려면 얕은 복사(Shallow Copy)나 깊은 복사(Deep Copy)를 통해 새로운 집을 지어야(새 메모리 할당) 합니다.
--------------------------------------------------------------------------------
4. 함수에 값을 전달할 때 일어나는 일 (Call by Sharing)
함수에 변수를 인자로 넘길 때도 마찬가지입니다.
function changeName(person) {
    person.name = "New Name"; // (1) 내부 속성 변경
    person = { name: "Another Object" }; // (2) 재할당
}

let user = { name: "Original" };
changeName(user);
console.log(user.name); // 결과: "New Name"
많은 분들이 여기서 혼란을 겪습니다. "참조가 전달됐다면 (2)번에서 아예 새 객체로 바뀌어야 하는 거 아니야?"라고요.
여기서 중요한 개념은 Call by Sharing(공유에 의한 호출)입니다.
1. 함수를 호출할 때, 변수 user가 가진 주소값(참조)이 복사되어 매개변수 person에 전달됩니다. (마치 복사된 열쇠를 주는 것과 같습니다.)
2. (1)번 상황: 복사된 열쇠로 문을 열고 들어가 가구(속성)를 바꾸면, 원본 집의 가구도 바뀝니다.
3. (2)번 상황: person = ...person이라는 이름표를 **새로운 집(새 객체)**에 다시 붙이는 행위입니다. 원본 user가 가진 열쇠에는 아무런 영향을 주지 않습니다.
--------------------------------------------------------------------------------
5. 이 개념을 이해하면 무엇이 달라질까요?
변수를 '상자'가 아닌 '이름표와 주소'로 이해하는 순간, 개발자의 시야는 완전히 달라집니다.
 
1. 디버깅 속도 향상: "왜 값이 바뀌었지?"라는 의문이 들 때, 누가 같은 주소를 공유(참조)하고 있는지 추적할 수 있습니다.
2. 프레임워크 이해도 증가: React나 Vue 같은 최신 라이브러리가 왜 불변성(Immutability)을 강조하며, 상태 변경 시 왜 spread operator(...) 등을 써서 객체를 새로 만드는지 이해하게 됩니다. (참조가 바뀌어야 변경을 감지하니까요!)
3. 메모리 누수 방지: 더 이상 쓰지 않는 객체의 연결(참조)을 끊어주어 가비지 컬렉터(GC)가 청소하게 만드는 원리를 알게 됩니다.
 
--------------------------------------------------------------------------------
📝 요약
변수는 값을 담는 상자가 아니라, 값이 있는 곳을 가리키는 이름표(참조)입니다.
메모리는 거대한 물류 창고이며, 객체는 힙(Heap) 영역에 저장되고 변수는 그 주소만 가집니다.
= 연산자는 객체를 복사하는 게 아니라, 주소를 복사하여 연결하는 것입니다.
함수에 객체를 넘길 때는 참조값(주소)의 복사본을 넘기는 것입니다.
이제 코드를 짤 때 머릿속에 상자 대신 화살표와 지도를 그려보세요. 훨씬 더 명확한 데이터 흐름이 보일 것입니다.

 

반응형