댕글링 포인터 (dangling pointer) 란, 어떤 메모리를 가리키는 포인터가 남아있는 상황에서 일부 메모리를 해제해 버림으로써, 다른 개체가 할당받았을지도 모르는 메모리를 참조하게 된 포인터를 말합니다.
이전 글에서, 작성 도중 떠오른 하나의 질문을 던지고 글을 마무리하였습니다.
Q. 참조자로 참조를 했는데, 원본 값이 갑자기 사라지면 어떡해 …?
→ A.
결론부터 말하자면, Rust 프로그램에서는 컴파일러가 이러한 상황을 사전에 방지합니다.
Dangling pointers and wild pointers in computer programming are pointers that do not point to a valid object of the appropriate type. These are special cases of memory safety violations. More generally, dangling references and wild references are references that do not resolve to a valid destination.
→ 위키백과 ‘Dangling pointer’ 발췌
댕글링 포인터는 어떤 포인터가 가리키고 있는 메모리가 할당 해제되었으나 여전히 포인터는 그 주소를 가리키고 있는 상황을 말하고, 이 상황은 예측할 수 없는 행동을 발생시키므로 위험합니다. 참고로 이 경우 Linux 혹은 Unix에서는 segmentation fault가, Windows에서는 general protection fault가 발생할 수 있습니다.
포인터가 존재하는 언어에서는 자칫 잘못했다가는 이러한 댕글링 포인터를 만들기 쉽습니다. 다들 그렇듯이 저도 과거에 segmentation fault가 발생해서 잘못된 부분을 찾느라 고생했던 기억이 여럿 있기 때문에, 이를 사전에 방지해준다는 Rust가 더욱 매력적으로 다가오기도 했습니다..ㅋㅋㅋ
러스트에서는 어떤 데이터의 참조자를 만들면, 해당 참조자가 스코프를 벗어나기 전에 데이터가 먼저 스코프를 벗어나는지 컴파일러에서 확인하여 댕글링 참조가 생성되지 않도록 보장합니다.
교재의 예시를 통해 알아보도록 하겠습니다.
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
위 코드는 에러가 발생합니다:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
5 | fn dangle() -> &'static String {
| +++++++
For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error
아직 다루지 않은 라이프타임이라는 내용이 에러 메시지에 등장하는데, 라이프타임은 10장에서 다룰 예정이니 일단 무시하도록 하겠습니다. 이 코드가 문제가 되는 이유를 알려주는 핵심 내용은 다음과 같습니다:
this function's return type contains a borrowed value, but there is no value for it to be borrowed from.
(해석: 이 함수는 빌린 값을 반환하고 있으나, 빌린 실제 값이 존재하지 않습니다.)
dangle 함수에서 어떤 일이 일어나는지 주석을 통해 알아보도록 하겠습니다:
fn dangle() -> &String { // dangle은 String의 참조자를 반환합니다
let s = String::from("hello"); // s는 새로운 String입니다
&s // String s의 참조자를 반환합니다
} // 여기서 s는 스코프 밖으로 벗어나고 버려집니다. 해당 메모리는 해제됩니다.
// 위험합니다!
s는 dangle 함수 내에서 생성됐기 때문에, 함수가 끝날 때 할당 해제됩니다. 하지만 코드에서는 &s를 반환하려 했고, 이는 유효하지 않은 String을 가리키는 참조자를 반환하는 행위이기 때문에 에러가 발생합니다.
따라서, 이런 경우엔 String을 직접 반환해야 합니다:
fn no_dangle() -> String {
let s = String::from("hello");
s
}
굳이 참조자를 반환하지 않고, String을 직접 반환해서 소유권을 이동시키는 방식으로 문제를 해결할 수 있습니다.
댕글링 참조가 발생하지 않도록 사전에 방지해주기도 하고, 간단한 해결책 또한 존재하기 때문에 코드를 작성하는 입장에서는 이는 엄청난 이점이라 생각됩니다!
읽어주셔서 감사합니다 🙇♂️
https://en.wikipedia.org/wiki/Dangling_pointer
https://doc.rust-kr.org/ch04-02-references-and-borrowing.html