两者。
一个术语:在讨论一个类型的内存布局时,不应该谈论stack vs heap,而应该谈论inline vs offline1:
-
inline 表示数据就在这里,
-
离线意味着数据在指针后面(无论它指向哪里)都可用。
一个简单的例子,整数是内联存储的:
// i32
+---+
| 3 |
+---+
一个典型的struct Point { x: i32, y: i32 }也内联存储:
// Point
+---+---+
| x | y |
+---+---+
String,通常表示为 struct String { data: *mut u8, len: usize, cap: usize }, 在线和离线存储:
// String
+-------+-------+-------+
| data | len | cap |
+-------+-------+-------+
|
\
+-------------+
|Hello, World!|
+-------------+
inline 部分是 3 个指针的存储空间,offline 部分是堆分配的记录,这里包含字符串 "Hello, World!" 的内容。
然而,inline 并不总是意味着 stack。一个Box<Point>:
// Box<Point>
+-------+
| data |
+-------+
|
\
+---+---+
| x | y |
+---+---+
将其Point(内联存储其数据成员)存储在堆上!
同样,离线并不总是意味着堆:
fn main() {
let i = 3;
let r = &i;
}
这里r是一个引用(指针),它指向i,而i在栈上!
1是的,我正在编造,更好的条款将不胜感激。
那么,回到问题:
它还说String 存储在堆上,因为大小未知并且可以变异。
这是一个近似值,如上所述,String 有一些数据内联(指针、长度和容量)和一些在堆上(字符串内容)。
“复合”数据结构(例如包含String 的数组)存储在哪里?数组的大小是固定的,但是数组的组成部分可以改变大小。
let array: [String; 3] = ["A","B","C"];
它同时存储在堆栈和堆中:
// [String; 3]
+-------+-------+-------+-------+-------+-------+-------+-------+-------+
| data | len | cap | data | len | cap | data | len | cap |
+-------+-------+-------+-------+-------+-------+-------+-------+-------+
| | |
\ \ \
+-+ +-+ +-+
|A| |B| |C|
+-+ +-+ +-+
这是 9 个指针的内联数据(在堆栈上),以及堆上的 3 个单独分配。
这种“复合”数据类型的存储位置的规则是什么?
数据成员总是内联,指针和引用可能指向离线数据,这些数据可能在堆上,在栈上等等......