레퍼런스

언어마다 구현의 차이는 있지만, ‘메모리에 저장된 실제 데이터를 가리키는 변수’ 를 의미힌다.

파이썬에서 레퍼런스는 객체를 가리키는 변수이다. 해당 객체가 레퍼런스 카운팅을 통해 레퍼런스를 관리한다. 레퍼런스는 스택 영역에, 객체는 힙 영역에 각각 저장한다. 할당되는 순간(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