레퍼런스
언어마다 구현의 차이는 있지만, ‘메모리에 저장된 실제 데이터를 가리키는 변수’ 를 의미힌다.
파이썬에서 레퍼런스는 객체를 가리키는 변수이다. 해당 객체가 레퍼런스 카운팅을 통해 레퍼런스를 관리한다. 레퍼런스는 스택 영역에, 객체는 힙 영역에 각각 저장한다. 할당되는 순간(x = 9999999999
), 레퍼런스가 힙 영역의 객체를 가리키며 객체의 레퍼런스 카운트를 증가시킨다. 다른 레퍼런스를 선언하고 기존 레퍼런스를 할당(y = x
)해도 같은 일이 발생한다. 새로 만든 레퍼런스는 기존 레퍼런스가 가리키던 객체를 가리키게 되고(y -> 9999999999), 기존 객체의 레퍼런스 카운트가 증가한다. 새로운 값을 할당(y = 10000000000
) 하면 새로운 객체를 가리킬 뿐이다. 기존 레퍼런스와 객체는 변하지 않은 채 그대로이다(x == 9999999999)
// python
import sys
x = 9999999999
print(sys.getrefcount(x)) # 2
y = x
print(sys.getrefcount(x)) # 3
print(x, y) # 9999999999 9999999999
print(id(x), id(y)) # 4305906672 4305906672
y = 10000000000
print(x) # 9999999999
print(sys.getrefcount(x)) # 2
c++ 의 레퍼런스는 변수의 별칭이다. 레퍼런스를 통해 변수 데이터에 쉽게 접근하고 변경할 수 있다. 일반 변수는 할당되는 순간(int x = 10;
) int 크기 만큼 스택 영역의 공간을 받고 값(10)을 저장한다. 반면 레퍼런스는 할당(int &y = x;
)되더라도 (기본적으로)메모리 공간을 점유하지 않고 기존 변수가 쓰던 공간을 공유한다. 컴파일러가 레퍼런스와 변수를 동일한 것으로 간주하는 것이다. 때문에 레퍼런스로 값을 변경(y = 20
) 한다면 변수 값 역시 바뀐다.
// c++
#include <iostream>
int main () {
int x = 10;
int &y = x;
std::cout << x << std::endl; // 10
std::cout << y << std::endl; // 10
y = 20
std::cout << x << std::endl; // 20
std::cout << y << std::endl; // 20
}
js의 레퍼런스는 참조 타입(object 등)을 가리키는 변수이다. 레퍼런스는 콜 스택에, 참조 타입은 힙 영역에 저장한다. 할당되는 순간(let x = { value: 10 };
) 콜 스택에 레퍼런스를 저장하며 레퍼런스는 힙 영역에 저장된 객체의 메모리 주소를 들고 있다. 또 다른 레퍼런스를 할당(let y = x;
) 할때 기존 레퍼런스의 값을 복사해 새로운 레퍼런스가 들고 있으므로 동일한 객체를 참조하게 된다. 따라서 레퍼런스로 참조 타입 데이터를 변경하면(x.value = 20;
), 다른 레퍼런스에서 확인한 값도 달라진다. 파이썬 레퍼런스가 mutable한 객체를 참조하는 상황과 동일하다.
// js
let x = { value: 10 };
let y = x;
x === y; // true
x.value = 20;
console.log(obj2.value) // 20
refs
- #3 python의 메모리 할당과 관리 (Stack & Heap Memory)
- [C++] 참조자 (레퍼런스, Reference) - 파일의 IT 블로그
- [JS] 📚 Call by Value & Call by Reference (+ Call by Sharing)