大家好,我新开始学习rust语言一周,以前是个C程序员,现在有个很大的疑问: use std::os::raw::c_char; struct A { i:u32, s:[c_char:32] }; 我遇到了实例化的疑问:
- 如果我直接声明变量 let a = A{ i:0, s : [这里需要写32个c_char才行]}; 而事实上,大多数情况下,我不需要32个字符,所以这种方法好像不行
- 构造函数: 我希望能使用 let a = A::new(10, "abc");这样的语句来构造 impl A{ pub new(ii:u32, ss:&str) -> Self { //我发现,这里是不是需要先有一个临时的c_char数组,或者是CString之类的变量,先从sst复制进来,假定为ss2 //然后 A{ i: ii, s: ss}; //这里是不是又引入了一次复制 } 第二种方案是否能够优化掉一次复制? 谢谢
1
共 11 条评论, 1 页
评论区
写评论这是我第二次尝试应该得到的答案,够简单直接。谢谢
真是不能太详细了,我也都看明白了,接下来我得好好看看你推荐的这些个资料,其中那个Atomics_and_Locks我已经看过了。非常感谢
哈哈哈,可能我说的不是很清楚,我的意思是可以这样:
或者这样:
第一次是零初始化,因为对于基本上所有的类型,Rust 都要求在使用实例前进行初始化,即使是不管是什么值都是有效的整数类型,要不然编译器就可能会进行错误的代码优化,要绕过这个限制就需要
MaybeUninit
和复杂一点的代码:a 被初始化(ii=0, s=[0;32])后,a.s 被修改,修改之后再返回,不知道你疑惑的具体是什么?
哈哈,就是跳到定义啦。
Box 这个类型意思就是,我会在堆上申请足够的空间,然后放入 T,如果我没有被移动(赋值)到其他地方的话,我会在块 (
{ ...... }
) 结尾处自动释放申请的空间,如果你可以看一下输出的汇编,就会看到编译器在合适的位置自动插入的 std::ptr::drop_in_place。确实,这个网站可能了解的人不是很多,还没有人贡献多语言支持。我的浏览器一直有谷歌翻译插件,翻译效果还过得去,不过我之前听说彩云小译和 DeepL 这两个翻译效果更好,你可以试试!
已经有中文翻译的书的话,在线上的有Rust 程序设计语言、 Rust 语言圣经 、Google 的 Comprehensive Rust、Rust 秘典、Rust 原子变量和锁 和 Effective Rust 这些,实体书的话,官方的入门书已有翻译《Rust权威指南》,也有很多人推荐 《Rust程序设计》。
你的原问题涉及 C ffi 和 unsafe Rust,是比较进阶的知识了,如果你已经看完了官方的那本入门书,就可以看《Rust 秘典》了,这是官方的介绍 unsafe Rust 的进阶书。
当然是指已知输入的情况,即字符串字面量(&'static str),因为在C语言中,
char[]
本质是指针,可以直接指向静态内存的地址。然而Rust的数组是Owned的,无论如何都必须复制一份数据,除非换用&mut [c_char;32]
。而且事实上,如果你在写ffi与C交互的话,后者才更像C语言的数组,当然放在结构体里的话,内存布局就不对了,所以如果追求性能还是得unsafe。--
👇
kipade: 如何提前到编译期?恐怕有问题吧,运行时的输入无法在编译期得知
如何提前到编译期?恐怕有问题吧,运行时的输入无法在编译期得知
通过你的建议,我想我写出来了,像下边这样: pub fn new(ii:u32, ss: &str) -> Self { let s: &[i8] = unsafe { std::slice::from_raw_parts(ss.as_ptr().cast(), ss.len()) }; let mut a = A {i:ii, s:[0;32]}; //第一次复制 let mut i = 0; for c in s { //第二次复制 a.s[i] = *c; i = i+1; } a } 那我有两个问题:
另外就是,你提到“我看了一下 CString 里面是一个 Box<[u8]>,感觉会引来不必要的内存申请。”,你看了一下,这一下是在哪儿看到的?然后,感觉会带来不必要的内存申请,如果有,谁负责释放?是否真的会被自动释放?
如果你已经过了《Rust 编程语言》,推荐这个网站,里面有很多常用类型的内存布局,我感觉对从 C 这样的底层语言过来的朋友会很有帮助!
c_char
就是i8
,如果是零初始化的话,可以用[0; 32]
。注意 Rust 中一个 &str 中间是可以有
\0
的,可以先检查一下。&str
底层是一个&[u8]
(可以用.as_bytes()
获取到),但是c_char
是i8
,可能需要来个unsafe
:再用
tmp_arr[..s.len()].copy_from_slice(s)
复制。不想用 unsafe 的话也可以一个一个复制,虽然看起来有点蠢,但是编译器非常聪明,会直接把这个循环优化成 memcopy!我看了一下 CString 里面是一个 Box<[u8]>,感觉会引来不必要的内存申请。
语义上来说是有一次复制,但是嘛,编译器非常聪明!
Rust 中的 String 与 C 的char* 有很大不同,其一就是 String 必定是在堆上的。而C的数组本质是指针,且也不区分堆栈内存。
所以对于你的方法2,可以写一个const fn来从str转换到c_char数组,这样一部分运算会提前到编译期。但是复制是不可避免的,即使是在C中那也是因为指针可以乱指,而这经常引发内存安全问题(可以用unsafe做到类似的事,但是unsafe)。
先定义一个临时数组变量,然后在用数组构造结构体,一般来讲编译器会直接优化掉临时变量的移动(或复制),就相当于是直接在A结构体分配的内存上直接初始化的
但是临时数组
[c_char;32]
的完整初始化和从&str
到[c_char;32]
的复制是没法避免的