Interfejs do C, zgubiony jeden blok na stercie.

0

Cześć,
Uczę się interfejsować Rusta z C, bo nie chcę więcej pisać w C, a jednak chciałbym rozwijać niektóre projekty i być może później przepisywać resztę kodu. Napisałem sobie prosty przykład testowy:

use std::str;
use std::slice;

#[repr(C)]
struct Window {}

extern "C" {
    fn strlen(cstr: *const u8) -> usize;
}

struct Stream {
    handle: Option<*const Window>,
    name: String
}

fn prefix_eq(prefix: &String, s: &String) -> bool {
    s.len() >= prefix.len() && s[0..prefix.len()] == *prefix
}

impl Stream {
    fn new(name: String) -> Self {
        Stream { handle: None, name: name }
    }

    fn try_window(&mut self, handle: *const Window, name: String) -> bool {
        if self.handle.is_none() && prefix_eq(&self.name, &name) {
            self.handle = Some(handle);
            true
        } else {
            false
        }
    }

    fn try_key(&mut self, handle: *const Window, key: u8) {
        self.handle.map(|my_handle| {
            if handle == my_handle {
                println!("hit {} for {:?}", str::from_utf8(&[key]).unwrap(), handle);
            }
        });
    }
}

fn ptr2str(ptr: *const u8) -> String {
    unsafe {
        String::from(str::from_utf8(slice::from_raw_parts(ptr, strlen(ptr))).unwrap())
    }
}

#[no_mangle]
extern "C"
fn init_stream(name: *const u8) -> Box<Stream> {
    Box::new(Stream::new(String::from(ptr2str(name))))
}

#[no_mangle]
extern "C"
fn win2stream(s: &mut Stream, handle: *const Window, name: *const u8) -> bool {
    s.try_window(handle, ptr2str(name))
}

#[no_mangle]
fn key2stream(s: &mut Stream, handle: *const Window, key: u8) {
    s.try_key(handle, key);
}

#[no_mangle]
fn free_streams(_s: Box<Stream>) {}

I po stronie C:

#include <stdio.h>

typedef struct stream *Stream;
typedef struct win *Window;
typedef unsigned char u8;

Stream init_stream(const char* win_name);
int win2stream(Stream s, Window win, const char *name);
void key2stream(Stream s, Window win, u8 key);
void free_streams(Stream s);

int main() {
    Stream s = init_stream("foowin");
    Window w = (Window) 0xafa;

    printf("%d\n", win2stream(s, w, "caca"));
    key2stream(s, w, 's');

    printf("%d\n", win2stream(s, w, "foowind"));
    key2stream(s, w, 'd');

    free_streams(s);
}

Kod działa poprawnie, co bardzo mnie cieszy. Chciałem się upewnić, że dobrze zwalniam pamięć i valgrind zgłosił mi jeden wyciek pamięci. Co ciekawe w miejscu dość niespodziewanym (w try_key):

==53920== Memcheck, a memory error detector
==53920== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==53920== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==53920== Command: ./test_streams
==53920== 
hit d for 0xafa
0
1
==53920== 
==53920== HEAP SUMMARY:
==53920==     in use at exit: 1,024 bytes in 1 blocks
==53920==   total heap usage: 6 allocs, 5 frees, 5,177 bytes allocated
==53920== 
==53920== 1,024 bytes in 1 blocks are still reachable in loss record 1 of 1
==53920==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==53920==    by 0x169172: _ZN3std4sync4once4Once15call_once_force28_$u7b$$u7b$closure$u7d$$u7d$17h78adb5c909ad3e88E.llvm.718437650808914235 (alloc.rs:89)
==53920==    by 0x11B014: std::sync::once::Once::call_inner (once.rs:434)
==53920==    by 0x12187D: std::io::stdio::_print (once.rs:334)
==53920==    by 0x11C2CC: stream::Stream::try_key::{{closure}} (in /home/lew/tools/dwm/test_streams)
==53920==    by 0x1491E9: core::option::Option<T>::map (in /home/lew/tools/dwm/test_streams)
==53920==    by 0x11C1CB: stream::Stream::try_key (in /home/lew/tools/dwm/test_streams)
==53920==    by 0x11C46A: key2stream (in /home/lew/tools/dwm/test_streams)
==53920==    by 0x11BF1F: main (in /home/lew/tools/dwm/test_streams)
==53920== 
==53920== LEAK SUMMARY:
==53920==    definitely lost: 0 bytes in 0 blocks
==53920==    indirectly lost: 0 bytes in 0 blocks
==53920==      possibly lost: 0 bytes in 0 blocks
==53920==    still reachable: 1,024 bytes in 1 blocks
==53920==         suppressed: 0 bytes in 0 blocks
==53920== 
==53920== For lists of detected and suppressed errors, rerun with: -s
==53920== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Istotnie, gdy usunąłem printa, wyciek znikł, a gdy próbowałem zrobić podobnie tylko bez interfejsowania do C (czyli z mapem i closure), problem nie wystąpił. Ktoś ma pomysł skąd ten problem i jak mu zaradzić?

EDIT: udało mi się obejść problem używając printf z libc, myśląc, że pewnie Rustowe Stdout ma jakiś bufor, który nie jest zwalniany. Zrobiłem tak:

unsafe { printf("Hit '%c' for %x\n\0".as_ptr(), key as usize, handle) };

I działa. Bajt zerowy okazał się konieczny, bo as_ptr() nie zwraca bajtu zerowego na końcu. To rozwiązalo problem, ale wciąż ciekawi mnie co mógłbym zrobić, żeby jednak użyć rusowego println!

0

A to jest na pewno wyciek?

 by 0x169172: _ZN3std4sync4once4Once15call_once_force28_$u7b$$u7b$closure$u7d$$u7d$17h78adb5c909ad3e88E.llvm.718437650808914235 (alloc.rs:89)

Wygląda mi to jak jakiś współdzielony stan, który nie jest zwalniany, bo nie wołasz żadnej funkcji czyszczącej Rustowe globale.

W jaki sposób linkujesz kod rustowy do kodu z C? Statyczna libka?

0
slsy napisał(a):

A to jest na pewno wyciek?

 by 0x169172: _ZN3std4sync4once4Once15call_once_force28_$u7b$$u7b$closure$u7d$$u7d$17h78adb5c909ad3e88E.llvm.718437650808914235 (alloc.rs:89)

Wygląda mi to jak jakiś współdzielony stan, który nie jest zwalniany, bo nie wołasz żadnej funkcji czyszczącej Rustowe globale.

W jaki sposób linkujesz kod rustowy do kodu z C? Statyczna libka?

Jak zwał tak zwał,. W kazdym razie nie jest zwalniane. Tak, jako statyczna bibioteka.

streamtest:
      rustc stream.rs --crate-type staticlib
      gcc test_streams.c libstream.a -o test_streams
      RUST_BACKTRACE=1 ./test_stream
1

Spróbuj stworzyć funkcję cleanup wołaną w kodzie z C, która będzie wołała io::cleanup. Nie widzę innego rozwiązania (może w dynamiczną libką by zadziałało), bo tak ogólnie działają statyczne libki, które nie mają żadnego mechanizmu czyszczenia globali, bo twój jedyny łącznik między kodem z C a Rustowym to wystawione przez ciebie funkcje

1

Jak @slsy pisał, masz alokację bufora do pisania - https://doc.rust-lang.org/1.72.0/src/std/io/stdio.rs.html#322. Nie za bardzo widzę jak da się to zwolnić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1