uninitialized
러스트는 객체의 초기화, 혹은 객체를 참조하기 전에 객체를 생성하는 것을 강제한다. 그렇게 해야 초보자가 하기 쉬운 쓰레기값을 가진 객체를 사용하는 실수를 방지하기 위해서이다.
하지만 항상 그렇게 하는 것은 피곤할 수 있고, 초기화를 안해도 되는 객체 또한 있기 마련이다. 이를 위해서 Rust에서는 uninitialized함수를 제공한다.
fn main() {
let num = unsafe{std::mem::uninitialized::<i32>()};
println!("{}",num);
}
unitialized는 Rust 메모리 안전 관리 수칙에서 벗어나는 행위이기 때문에 '너 정말 이게 안전하지 않은 지 아니?라는 확인용인 unsafe구문 내에서만 사용할 수 있다. 이 코드를 실행하면 num의 값이 쓰레기 값인 걸 확인할 수 있다.
Finished dev [unoptimized + debuginfo] target(s) in 0.41s Running `target\debug\tes.exe` 0x00000086 PS C:\Users\qwead\Documents\tes> cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.00s Running `target\debug\tes.exe` 0x00000098 PS C:\Users\qwead\Documents\tes> cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.00s Running `target\debug\tes.exe` 0x000000f4 PS C:\Users\qwead\Documents\tes> cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.00s Running `target\debug\tes.exe` 0x0000008b
forget
uninitialized가 객체를 생성할 때 Rust 메모리 관리 규칙을 벗어나는 방법을 제공한다면 forget은 객체를 삭제할 때 Rust 메모리 관리 규칙에서 벗어나는 방법을 제공한다.
Rust의 메모리 관리 규칙은 스코프가 끝날 때 그 스코프가 소유한 객체도 메모리를 해제한다. 이때 일반적인 객체가 아닌 파일IO관련 객체 혹은 동적메모리를 사용하는 객체의 경우 객체가 죽을 때 객체의 drop메소드가 호출되어 객체 자신이 정리할 시간을 제공한다.
하지만 forget은 drop메소드를 호출하지 않고 객체를 해제한다.
fn main() {
{
let mut s = Vec::<u128>::new();
for _ in 0..std::u16::MAX as u32 * 16{
s.push(0u128);
}
let mut n = String::new();
println!("press any key");
std::io::stdin().read_line(&mut n);
}
let mut n = String::new();
println!("press any key");
std::io::stdin().read_line(&mut n);
}
메모리를 정상적으로 확인하는 것을 볼 수 있다. 하지만 코드 중간에 forget함수를 삽입하여 drop메소드를 안 호출하고 객체를 제거하면
fn main() {
{
let mut s = Vec::<u128>::new();
for _ in 0..std::u16::MAX as u32 * 16{
s.push(0u128);
}
let mut n = String::new();
println!("press any key");
std::io::stdin().read_line(&mut n);
std::mem::forget(s);
}
let mut n = String::new();
println!("press any key");
std::io::stdin().read_line(&mut n);
}
보다시피 메모리가 줄어 들지 않는 것을 확인할 수 있다.
이것들을 어떻게 사용할까?
여러군데 사용할 수 있는데 대표적으로는 합병정렬에서 사용할 수 있다.
fn sub_merge_sort<T>(source: *mut[T], memory:*mut[T])where T:PartialEq + PartialOrd{
let s:&mut[T] = unsafe{ std::mem::transmute::<_, &mut[T]>(source) };
let m:&mut[T] = unsafe{ std::mem::transmute::<_, &mut[T]>(memory) };
if s.len() > 1{
let len = s.len();
let (s1,s2,m1,m2);
unsafe{
s1 = std::mem::transmute::<_, &mut[T]>(&mut s[0..len/2]);
s2 = std::mem::transmute::<_, &mut[T]>(&mut s[len/2..len]);
m1 = std::mem::transmute::<_, *mut[T]>(&m[0..len/2]);
m2 = std::mem::transmute::<_, *mut[T]>(&m[len/2..len]);
}
sub_merge_sort(s1, m1);
sub_merge_sort(s2, m2);
let mem:&mut[T] = unsafe{ std::mem::transmute::<_, &mut[T]>(memory) };
let mut s1_it = s1.iter_mut();
let mut s2_it = s2.iter_mut();
let mut mem_it = mem.iter_mut();
let mut sv1= s1_it.next();
let mut sv2= s2_it.next();
loop{
let m = match mem_it.next(){
Some(v)=>v,
None=>break
};
let switch =
match (&sv1,&sv2){
(&None,& Some(_))=>2,
(&Some(_),& None)=>1,
(&Some(ref v1),&Some(ref v2))if v1 > v2=>2,
(&Some(ref v1),&Some(ref v2))if v1 <= v2=>1,
_=>{break;}
};
match switch{
2=>{
//let sv = &mut sv2;
let v = sv2.unwrap();
std::mem::swap(m,v);
sv2 = s2_it.next()
},
1=>{
let v = sv1.unwrap();
std::mem::swap(m,v);
sv1 = s1_it.next()
}
_=>{}
}
}
let mut mi = m.iter_mut();
let mut si = s.iter_mut();
while let Some(sv) = si.next(){
let mv = mi.next().unwrap();
std::mem::swap(sv,mv);
}
}
}
fn merge_sort<T>(source:&mut[T])where T:PartialEq + PartialOrd{
let mut tempMemory:Vec<T> = Vec::with_capacity(source.len());
for _ in 0..source.len(){
unsafe{tempMemory.push(std::mem::uninitialized());}
}
sub_merge_sort(source as *mut[T], tempMemory.as_mut_slice() as *mut[T]);
for it in tempMemory{
std::mem::forget(it);
}
}