首页 文章

为什么C结构在从Rust读取时返回错位数据?

提问于
浏览
1

我想在Rust中包装一个C函数 . C函数 struct elem* get_list() 返回以下结构:

struct elem {
    char data[5],
    struct elem* next
};

在Rust中,我已按以下方式声明了该函数 . C函数的声明返回 *const c_void ,如旧版本的Rust文档中所述,在撰写本文时我无法找到 . 我尝试返回 *const elem 并使用指针,实现相同的结果:

extern "C" {
    pub fn get_list() -> *const c_void;
}

struct表示链表, next 是指向列表下一个元素的指针 . 在Rust内部,我以下列方式声明了结构:

#[repr(C)]
pub struct elem {
    pub data: [u8; 5],
    pub next: *const c_void,
}

该函数返回 *const c_void 指向链表的第一个元素(类型为 elem ) . 我正在尝试使用以下代码读取链表的元素:

let head = get_list();
while !head.is_null() {
    let el: &elem = mem::transmute(head);
    let str = el.data;
    let str = CStr::from_bytes_with_nul(&str).unwrap();
    //do something
    head = el.next();
}

这会读取垃圾数据 - 指针未正确对齐,字符串都是错误且非空终止,下一个指针会导致随机数据(当从C直接调用函数时,列表具有不同的大小) .

我尝试使用函数返回一个指向 elem 的指针并且仅使用指针,我尝试从 el 的地址转换 str - 它总是读取相同的垃圾数据 . 如何使其正确对齐?

我知道如何使用指针而不是数组来实现它,这就是在Rust文档中演示它的方式,但我无法更改C代码 .

1 回答

  • 1

    在我为这个案例编写了一个示例库之后,我发现它不是一个外部问题,而是一个 CStr . 正如在示例中修复的那样,我将缓冲区切片到第一个NUL终结符的位置,我提供了我为正确的externing编写的示例 .

    list.c

    #include <stdlib.h>
    #include <string.h>
    
    struct elem {
        char data[5];
        struct elem* next;
    };
    
    struct elem* get_list() {
        struct elem* head = malloc(sizeof(struct elem));
        strcpy(head->data, "1");
    
        struct elem* el = malloc(sizeof(struct elem));
        head->next = el;
    
        strcpy(el->data, "2");
    
        el->next = malloc(sizeof(struct elem));
        el = el->next;
        strcpy(el->data, "3");
        el->next = NULL;
    
        return head;
    }
    

    main.rs

    use std::ffi::CStr;
    
    #[repr(C)]
    pub struct elem {
        pub data: [u8; 5],
        pub next: *const elem
    }
    
    #[link(name = "list", kind = "static")]
    extern {
        pub fn get_list() -> *const elem;
    }
    
    fn main() {
        unsafe {
            let mut list = get_list();
            // Note, that, if we call from_bytes_with_nul it will throw  
            // an NulInternal error, therefore,
            // we have to slice the buffer to the first NUL-terminator
            while !list.is_null() {
                let mut null_pos = (*list).data.len() - 1;
                {
                    for i in 0..(*list).data.len() {
                        if (*list).data[i] == 0 {
                            null_pos = i + 1;
                            break
                        }
                    }
                }
                let str = CStr::from_bytes_with_nul(
                              (*list).data[..null_pos]
                          ).unwrap();
                println!("{:?}", str);
                list = (*list).next;
            }
        }
    }
    

    产量

    "1"
    "2"
    "3"
    

    实施的关键方面:

    • 定义相同的结构,用 #[repr(C)] 注释,因此它将以与C 1相同的方式对齐 .

    • 定义extern函数以返回指向结构的const指针 .

    • 使用指针而不是 std::mem::transmute

    • 小心使用空指针和终结符

相关问题