< 返回版块

cybertheye 发表于 2024-04-23 17:09

这里的这个图解是不是有问题

Box


let t: (i32, String) = (5, “Hello”.to_string);
let mut b = Box::new(t);

t在内存中还能转移的?t在栈上,被Box包了,就变到了堆上了?

我感觉应该是这样的

有什么办法可以验证

评论区

写评论
星夜的蓝天 2024-04-25 10:26

看一下汇编就好了

#[inline(never)]
pub fn test_box() -> Box<(i32, String)>{
    let t: (i32, String) = (2, "Hello".to_string());
    let b = Box::new(t);
    
    return b;
}
playground::test_box:
	push	r14
	push	rbx
	push	rax
	mov	r14, qword ptr [rip + __rust_no_alloc_shim_is_unstable@GOTPCREL]
	movzx	eax, byte ptr [r14]
	mov	edi, 5
	mov	esi, 1
	call	qword ptr [rip + __rust_alloc@GOTPCREL]
	test	rax, rax
	je	.LBB0_6
	mov	rbx, rax
	mov	byte ptr [rax + 4], 111
	mov	dword ptr [rax], 1819043144
	movzx	eax, byte ptr [r14]
	mov	edi, 32
	mov	esi, 8
	call	qword ptr [rip + __rust_alloc@GOTPCREL]
	test	rax, rax
	je	.LBB0_2
	mov	dword ptr [rax], 2
	mov	qword ptr [rax + 8], 5
	mov	qword ptr [rax + 16], rbx
	mov	qword ptr [rax + 24], 5
	add	rsp, 8
	pop	rbx
	pop	r14
	ret

逐段分析

	push	r14
	push	rbx
	push	rax
	mov	r14, qword ptr [rip + __rust_no_alloc_shim_is_unstable@GOTPCREL]

保存现场,访问了一个GOT表的地址,不清楚作用

	movzx	eax, byte ptr [r14]
	mov	edi, 5
	mov	esi, 1
	call	qword ptr [rip + __rust_alloc@GOTPCREL]

堆分配一个5字节,1字节对齐的空间

	test	rax, rax
	je	.LBB0_6

错误处理,忽略

	mov	rbx, rax
	mov	byte ptr [rax + 4], 111
	mov	dword ptr [rax], 1819043144

保存rax指针,即字符串数据指针,做字符串赋值,111对应'o',那串数字转16进制的每个字节就是对应“Hell"

	mov	edi, 32
	mov	esi, 8
	call	qword ptr [rip + __rust_alloc@GOTPCREL]
	test	rax, rax
	je	.LBB0_2

分配一个32字节的空间,给box元组使用

	mov	dword ptr [rax], 2
	mov	qword ptr [rax + 8], 5
	mov	qword ptr [rax + 16], rbx
	mov	qword ptr [rax + 24], 5
	add	rsp, 8

元组赋值,编译器优化后2不放置在栈上,直接移动到堆上了,字符串也是直接在堆上创建了,不需要再搬一次

bestgopher 2024-04-23 20:40

传入Box::new函数copy一份栈上的数据到堆上不是很正常吗?

作者 cybertheye 2024-04-23 19:41
fn main(){

let t: (i32, String) = (5, "Hello".to_string());
let mut b = Box::new(t);
println!("{:?}",t);
println!("{:?}",b);
}

t不能用了,如果要想t还可能用,需要 let mut b = Box::new(t.clone()); 这样就懂了,这里其实就是转移了,本质是 copy trait吧,其实也就是数据从栈上转移到堆上了

--
👇
cybertheye: 那t还是t是吧

--
👇
ggggjlgl: 转移应该是move语义,move会使b拥有原属t的元组并且要求编译器将t视作未初始化状态(但可能为了效率,好像并没有重置t的内存)。

只是Box这个智能指针会强制将数据分配在堆上,所以应该是发生了对t拥有的元组的隐式浅拷贝,Rust不会隐式拷贝复杂数据,这里应该是拷贝了i32、ptr_str、cap、len这几个机器字。

作者 cybertheye 2024-04-23 19:37

所以是发生了数据拷贝

--
👇
Bai-Jinlin: 写成c可能就好理解了

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  char *p;
  size_t cap;
  size_t len;
} String;

String new_string(char *src) {
  size_t len = strlen(src);
  char *p = malloc(len);
  memcpy(p, src, len);
  String str = {
      .p = p,
      .cap = len,
      .len = len,
  };
  return str;
}
void free_string(String *str) { free(str->p); }

struct A {
  int32_t a;
  String b;
};

typedef struct {
  void *p;
} Box;

Box new_box_from_a(struct A a) {
  size_t len = sizeof(a);
  void *p = malloc(len);
  memcpy(p, &a, len);
  Box box = {.p = p};
  return box;
}
void free_box(Box *box) { free(box->p); }

int main() {
  String str = new_string("Hello");
  struct A t = {.a = 5, .b = str};
  Box b = new_box_from_a(t);

  free_string(&((struct A *)(b.p))->b);
  free_box(&b);
  return 0;
}
作者 cybertheye 2024-04-23 19:34

那t还是t是吧

--
👇
ggggjlgl: 转移应该是move语义,move会使b拥有原属t的元组并且要求编译器将t视作未初始化状态(但可能为了效率,好像并没有重置t的内存)。

只是Box这个智能指针会强制将数据分配在堆上,所以应该是发生了对t拥有的元组的隐式浅拷贝,Rust不会隐式拷贝复杂数据,这里应该是拷贝了i32、ptr_str、cap、len这几个机器字。

作者 cybertheye 2024-04-23 19:33

元组好像是在栈上吧

--
👇
ggggjlgl: 等大神回复下怎么验证。 还有t声明语句执行完之后,这个元组被分配在栈上还是堆上,我一直觉得这种简单数据会在栈上,但是看完内存现在觉得这个元组一开始就在堆上。

Bai-Jinlin 2024-04-23 19:23

写成c可能就好理解了

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  char *p;
  size_t cap;
  size_t len;
} String;

String new_string(char *src) {
  size_t len = strlen(src);
  char *p = malloc(len);
  memcpy(p, src, len);
  String str = {
      .p = p,
      .cap = len,
      .len = len,
  };
  return str;
}
void free_string(String *str) { free(str->p); }

struct A {
  int32_t a;
  String b;
};

typedef struct {
  void *p;
} Box;

Box new_box_from_a(struct A a) {
  size_t len = sizeof(a);
  void *p = malloc(len);
  memcpy(p, &a, len);
  Box box = {.p = p};
  return box;
}
void free_box(Box *box) { free(box->p); }

int main() {
  String str = new_string("Hello");
  struct A t = {.a = 5, .b = str};
  Box b = new_box_from_a(t);

  free_string(&((struct A *)(b.p))->b);
  free_box(&b);
  return 0;
}
asuper 2024-04-23 18:45

Box类型的注释: A pointer type that uniquely owns a heap allocation of type T.

Box::new()的注释: Allocates memory on the heap and then places x into it.

毫无疑问Box的内容是存在堆上的,栈上的只是指针。

至于Box::new()是怎么拷贝过去的,我不清楚

ggggjlgl 2024-04-23 18:37

等大神回复下怎么验证。 还有t声明语句执行完之后,这个元组被分配在栈上还是堆上,我一直觉得这种简单数据会在栈上,但是看完内存现在觉得这个元组一开始就在堆上。

ggggjlgl 2024-04-23 18:33

转移应该是move语义,move会使b拥有原属t的元组并且要求编译器将t视作未初始化状态(但可能为了效率,好像并没有重置t的内存)。

只是Box这个智能指针会强制将数据分配在堆上,所以应该是发生了对t拥有的元组的隐式浅拷贝,Rust不会隐式拷贝复杂数据,这里应该是拷贝了i32、ptr_str、cap、len这几个机器字。

Shylock-Hg 2024-04-23 17:32

移动语义和堆栈没啥关系,不要搞混了

1 共 11 条评论, 1 页