openlang

댕글링 포인터 경고는 사라졌고 포인터는 남았다

래퍼를 통해 C++ 라이브러리에 이름을 넘기는데, 처음엔 한 줄로 CString::new(name).unwrap().as_ptr()라고 썼어요. rustc가 바로 임시 CString이 문장 끝에서 사라지니 포인터가 댕글링이 된다고 경고했어요. 그래, 맞는 말이에요. 그래서 지역 변수에 CString을 만들고 as_ptr()를 돌려주는 작은 함수로 뺐더니 경고가 사라졌고, 거의 안심했는데, 그러다 아무것도 안 바뀌었다는 걸 깨달았어요. 문자열이 이제 문장 끝이 아니라 함수 끝에서 죽을 뿐이에요.

use std::ffi::{c_char, CString};

fn name_ptr(s: &str) -> *const c_char {
    let c = CString::new(s).unwrap();
    c.as_ptr()
}

이건 경고 하나 없이 컴파일돼요. 린트는 한 표현식 안의 임시 값은 잡지만 함수 밖으로 나가는 바인딩된 지역 변수는 못 잡아요. 경고의 도움말에는 포인터를 반환할 때는 함수 안에서 바인딩하는 것만으로 부족하다고 적혀 있는데도요. 그리고 만약 C++ 쪽이 호출 중에 읽기만 하는 게 아니라 포인터를 저장까지 한다면 호출하는 쪽의 변수로도 모자라는데, 어느 쪽인지 아직 몰라요. 문서가 거의 없거든요. 일단 CString을 래퍼 구조체 안에 핸들과 나란히 두어서 수명을 같게 맞춰 두고, 다들 뭐라고 하는지 볼게요.

이어지는 대화 1

서로의 근거를 따라
  1. 고유 링크

    소유자 수를 세어 보면 단순해져요. 구조체에 넣으면 소유자가 하나이고 수명이 핸들과 같아요. 라이브러리가 그 핸들이 살아 있는 동안만 포인터를 읽는다면 그게 맞아요. 핸들을 닫은 뒤에도 포인터를 들고 있거나 직접 해제한다면 틀려요.

    라이브러리가 들고 있는다면 소유권을 명시적으로 넘기세요. CString::into_raw로 Rust 쪽 소유를 내려놓고, 라이브러리가 다 쓰면 같은 포인터에 CString::from_raw를 정확히 한 번 불러서 되찾아요. 내보내는 호출 하나, 돌려받는 호출 하나예요. 할당이 Rust 할당자에서 나왔으니 그 포인터가 C의 free에 닿으면 안 돼요. 라이브러리에서 언제 다 썼는지 알려 주는 게 없다면, 둘 중 하나를 고르기 전에 그것부터 알아내야 해요.

같은 질문의 다른 갈래

  1. getline은 실패해도 버퍼를 호출자에게 남긴다

    POSIX getline은 넘겨받은 포인터를 통해 버퍼를 할당하거나 키우며, 그 버퍼는 첫 호출부터 호출자의 것입니다. -1을 반환하는 호출도 예외가 아닙니다. 명세는 해제를 애플리케이션에 맡기고, Linux 매뉴얼 페이지는…

    답글 4c
  2. 같은 인스턴스를 가리키는 인터페이스 참조와 객체 참조

    오래된 Delphi 코드에서 제가 가장 자주 만나는 수명 실수는 한 인스턴스에 두 가지 소유 규칙을 섞는 데서 나옵니다. TInterfacedObject를 상속한 클래스는 인터페이스 참조 카운트가 0이 될 때 해제됩니다. 클…

  3. 같은 sink 호출인데 한 번은 복사, 한 번은 이동

    Nim 2는 복사냐 이동이냐를 호출 지점마다 정한다. proc 단위로 정하지 않는다. 같은 인자, 같은 proc. 첫 호출은 a가 아직 쓰이니 복사한다. 둘째 호출은 이동한다. 호출 지점에는 어느 쪽인지 아무 표시가 없다.…

    답글 2nim