안녕하세요 현재 프로그랩 언어 Rust를 주언어로 사용하고 있는 개발자입니다.
Rust는 c언어의 단점을 보완하고자 나온 언어로 현재는 마이크로소프트나 구글에서도 여러 오픈소스 프로젝트를 내며 사랑받고 있는 언어입니다.
저 또한 Rust만의 특징과 사전 오류탐지와 같은 기능에 매료되어서 주언어로 계속 사용되고 있습니다.
어느 날 제가 개인 프로젝트를 진행하다가 이런 문제점이 발생했습니다.
struct Test{
a: i32,
}
impl Drop for Test{
fn drop(&mut self){
println!("droped");
}
}
static mut HELLO: Test = Test {
a: 0,
};
fn main(){
println!("main hello")
}
저는 여기서 자연스럽게 HELLO
가 프로그램 종료하면서 drop
함수를 실행하고 싶었습니다.
하지만 Rust는 static변수에 대해서는 drop
함수를 수행하지 않는 문제점이 있었습니다.
ManuallyDrop
도 해보고 시도를 하였지만 결국 태생부터가 정적변수여서 그렇게 할수 없더라고요.
그렇게 고민하다가 나온 해결책이 ctor
,dtor
이었습니다.
ctor
,dtor
은 linker가 만들어주는 segment로 전자는 프로그램 시작시, 후자는 프로그램 종료시에 실행이 됩니다. 그래서 이 영역에 함수를 정의를 하면 적절한 시기에 함수를 수행을 합니다. 즉 이 영역들을 적절히 사용을 하면 drop
함수를 수행할 수 있게 되는 겁니다.
각 영역의 이름은 저장되는 파일 포멧에 달라집니다. 파일 포멧에는 Mach O,ELF등이 있습니다.
ELF
의 segment중에서는 .init_func.NNNN
,.fini_func.NNNN
가 있습니다. 각각 프로그램 실행전, 프로그램 실행후에 해당하는 영역입니다.
Mach o
와 같은 경우 __DATA,__mod_init_func
,__DATA,__mod_term_func
에 해당합니다.
이를 이용해서 코드를 작성하면 다음과 같습니다.
struct Test{
a: i32,
}
impl Drop for Test{
fn drop(&mut self){
println!("droped");
}
}
static mut HELLO: Test = Test {
a: 0,
};
extern "C" fn __init_hello(){
println!("init program");
}
extern "C" fn __fini_hello(){
unsafe{
drop(&HELLO);
}
println!("finish program");
}
#[link_section = "__DATA,__mod_init_func"]
#[used]
pub static __init_section: extern "C" fn() = __init_hello;
#[link_section = "__DATA,__mod_term_func"]
#[used]
pub static __fini_section: extern "C" fn() = __fini_hello;
fn main() {
unsafe{
println!("HELLO a: {}",HELLO.a);
}
println!("main hello")
}
실행 결과 다음과 같이 나옵니다.
init program
HELLO a: 0
main hello
finish program
program 시작시와 종료시에 함수 호출은 정상적으로 나오지만 drop
함수가 호출이 되지 않았네요. ㅠㅠ
일단 코드 설명은 다음과 같습니다.
extern "C" fn __init_hello(){
println!("init program");
}
extern "C" fn __fini_hello(){
unsafe{
drop(&HELLO);
}
println!("finish program");
}
먼저 linker 영역에 함수를 삽입하기 위해서는 외부에서도 사용 가능해야 하기 때문에 extern
을 부여하였습니다.
#[link_section = "__DATA,__mod_init_func"]
#[used]
pub static __init_section: extern "C" fn() = __init_hello;
#[link_section = "__DATA,__mod_term_func"]
#[used]
pub static __fini_section: extern "C" fn() = __fini_hello;
그리고 그 함수들을 대입할 수 있도록 static 변수로 정의후 extern
함수 포인터를 대입을 합니다. 이때 link_section attribute를 통해 저장하기 원하는 영역을 지정할 수 있습니다. 저 같은 경우에는 맥을 사용해서 파일 포맷 Mach o
에 맞춰서 진행했습니다.그리고 used
를 통해 컴파일러 상에서 무리한 최적화를 피하고 유지할 수 있게 합니다.
이렇게 함으로써 우리가 원하는 함수를 넣는게 가능해집니다!!!
이제 drop
만 해결하면 되는데 위에서 말했듯이 태생부터가 static이어서 그럴 수 가 없었습니다. 그래서 이를 강제로 할 수 있게 하는 방법이 있습니다.
바로 ptr::drop_in_place
입니다. 이 함수는 raw pointer를 기반해서 해당 객체를 lifetime과 상관없이 drop
할 수 있게 해주는 함수입니다. 이를 이용해서 코드를 재작성하면 다음과 같습니다.
struct Test{
a: i32,
}
impl Drop for Test{
fn drop(&mut self){
println!("droped");
}
}
static mut HELLO: Test = Test {
a: 0,
};
extern "C" fn __init_hello(){
println!("init program");
}
extern "C" fn __fini_hello(){
unsafe{
let t_ptr = &mut HELLO as *mut Test;
std::ptr::drop_in_place(t_ptr);
}
println!("finish program");
}
#[link_section = "__DATA,__mod_init_func"]
#[used]
pub static __init_section: extern "C" fn() = __init_hello;
#[link_section = "__DATA,__mod_term_func"]
pub static __fini_section: extern "C" fn() = __fini_hello;
fn main() {
unsafe{
println!("HELLO a: {}",HELLO.a);
}
println!("main hello")
}
init program
HELLO a: 0
main hello
droped
finish program
오!! drop
이 되었습니다. 이제 static 변수도 프로그램 종료시 drop
이 가능해집니다. 근데 코드 조금 지저분하긴 하네요. 이를 한번 macro로 작성해 봅시다.
macro_rules! static_drop {
($([$x:ident,$type:ty]),*) => {
extern "C" fn __fini_hello(){
$(
unsafe{
let t_ptr = &mut $x as *mut $type;
std::ptr::drop_in_place(t_ptr);
}
)*
println!("finish program");
}
#[cfg(target_os="macos")]
#[link_section = "__DATA,__mod_term_func"]
pub static __fini_section: extern "C" fn() = __fini_hello;
#[cfg(not(target_os="macos"))]
#[link_section = ".fini_array.65534"]
pub static __fini_section: extern "C" fn() = __fini_hello;
};
}
struct Test {
a: i32,
}
impl Drop for Test {
fn drop(&mut self) {
println!("droped_{}", self.a);
}
}
static mut HELLO: Test = Test { a: 0 };
static mut HELLO2: Test = Test { a: 2 };
static mut HELLO3: Test = Test { a: 3 };
static_drop!([HELLO, Test], [HELLO2, Test], [HELLO3, Test]);
fn main() {
unsafe {
println!("HELLO a: {}", HELLO.a);
}
println!("main hello")
}
HELLO a: 0
main hello
droped_0
droped_2
droped_3
finish program
macro를 써서 여러 개의 static변수를 손쉽게 정의를 하고 자연스럽게 drop
이 되는 것을 볼 수 있습니다. 물론 여기서 proc_macro까지 쓰면 더 멋있는 형태가 나오지만 여기까지 하도록 하겠습니다.
긴 글 읽어주셔서 감사합니다!!
감사합니다.