0x来自哪里?(where does 0x come from? [duplicate])
可能重复:
为什么十六进制的前缀为0x?我刚刚看到我的一个朋友发表的评论:
3x12=36 2x12=24 1x12=12 0x12=18
这让我很想知道..
为什么他们选择0x
作为十六进制数字的前缀? 这个决定背后有什么历史吗?Possible Duplicate:
Why are Hexadecimal Prefixed as 0x?I just saw a comment a friend of mine made:
3x12=36 2x12=24 1x12=12 0x12=18
Which made me wonder..
Why did they choose0x
as prefix for hexadecimal numbers? Is there any history behind this decision?
原文:https://stackoverflow.com/questions/4257661
最满意答案
我们来看一下这个简单的实现 :
struct Parent { count: u32, } struct Child<'a> { parent: &'a Parent, } struct Combined<'a> { parent: Parent, child: Child<'a>, } impl<'a> Combined<'a> { fn new() -> Self { let p = Parent { count: 42 }; let c = Child { parent: &p }; Combined { parent: p, child: c } } } fn main() {}
这将失败,稍微清理错误:
error: `p` does not live long enough --> src/main.rs:17:34 | 17 | let c = Child { parent: &p }; | ^ | note: reference must be valid for the lifetime 'a as defined on the block at 15:21... --> src/main.rs:15:22 | 15 | fn new() -> Self { | ^ note: ...but borrowed value is only valid for the block suffix following statement 0 at 16:37 --> src/main.rs:16:38 | 16 | let p = Parent { count: 42 }; | ^
要完全理解这个错误,你必须考虑如何在内存中表示值,以及在移动这些值时会发生什么。 我们用一些假设的内存地址来注释
Combined::new
,它们显示了值的位置:let p = Parent { count: 42 }; // `p` lives at address 0x1000 and takes up 4 bytes // The value of `p` is 42 let c = Child { parent: &p }; // `c` lives at address 0x1010 and takes up 4 bytes // The value of `c` is 0x1000 Combined { parent: p, child: c } // The return value lives at address 0x2000 and takes up 8 bytes // `p` is moved to 0x2000 // `c` is ... ?
c
应该怎么办? 如果值像p
一样移动,那么它将引用不再保证在其中具有有效值的内存。 任何其他代码片段都允许在内存地址0x1000存储值。 假设它是一个整数访问该内存可能会导致崩溃和/或安全错误,并且是Rust防止的主要错误类别之一。这正是生命预防的问题。 生命周期是一些元数据,允许您和编译器知道值在其当前内存位置有效多长时间。 这是一个重要的区别,因为这是Rust新手做的一个常见的错误。 生锈时间不是创建对象和被破坏之间的时间段!
作为一个比喻,以这种方式思考:在一个人的生活中,他们将居住在许多不同的地方,每个地点都有不同的地址。 生锈的生活关心你现在所在的地址,而不是每当你将来会死亡(尽管死亡也改变你的地址)。 每次你移动它是相关的,因为你的地址不再有效。
同样重要的是要注意,生命周期不会改变你的代码; 您的代码控制生命周期,您的生命周期不能控制代码。 幽默的说法是“生命是描述性的,而不是规范性的”。
我们用一些行号注释
Combined::new
,我们将用它来突出显示生命周期:{ // 0 let p = Parent { count: 42 }; // 1 let c = Child { parent: &p }; // 2 // 3 Combined { parent: p, child: c } // 4 } // 5
p
的具体寿命为1到4,其中我将代表[1,4]
)。c
的具体寿命为[2,4]
,返回值的具体寿命为[4,5]
。 有可能具有从零开始的具体生命周期 - 这将表示函数的一个参数的生命周期或者在块之外存在的东西。注意,
c
本身的寿命是[2,4]
,但它指的是一个寿命为[1,4]
。 只要引用值在引用值之前变得无效,这是很好的。 当我们尝试从块中返回c
时,会出现此问题。 这将超出其自然长度“超越”一生。这个新知识应该说明前两个例子。 第三个需要查看
Parent::child
的实现。 有机会,它看起来像这样:impl Parent { fn child(&self) -> Child { ... } }
这将使用终身检测来避免编写显式的通用生命周期参数 。 相当于:
impl Parent { fn child<'a>(&'a self) -> Child<'a> { ... } }
在这两种情况下,该方法表示将返回一个已被参数化的
Child
结构,并具有具体的self
生命周期。 换句话说,Child
实例包含一个引用,它创建它的Parent
,因此不能比Parent
实例活得更长。这也让我们认识到我们的创作功能真的是错误的:
fn make_combined<'a>() -> Combined<'a> { ... }
虽然你更有可能看到这个写成不同的形式:
impl<'a> Combined<'a> { fn new() -> Combined<'a> { ... } }
在这两种情况下,都没有通过参数提供生命周期参数。 这意味着
Combined
将被参数化的生命周期不受任何限制 - 它可以是任何呼叫者想要的。 这是无意义的,因为调用者可以指定'static
生命周期,没有办法满足该条件。如何解决?
最简单和最推荐的解决方案是不要将这些项目放在同一个结构中。 通过这样做,您的结构嵌套将模仿您的代码的生命周期。 将数据类型放在一起的结构中,然后提供允许您根据需要获取包含引用的引用或对象的方法。
有一个特殊情况下,生命周期跟踪是过度的:当你有堆放在堆上的东西。 例如,当您使用
Box<T>
时会发生这种情况。 在这种情况下,被移动的结构包含指向堆的指针。 指向值将保持稳定,但指针本身的地址将移动。 实际上,这并不重要,因为你总是遵循指针。租赁箱子或owning_ref箱子是表示这种情况的方法,但是它们要求基地址不会移动 。 这排除了突变向量,这可能导致重新分配和堆分配值的移动。
更多信息
将
p
移入结构体后,为什么编译器无法获取对p
的新引用,并将其分配给struct中的c
?在理论上可以做到这一点,这样做会引起大量的复杂性和开销。 每次对象被移动时,编译器都需要插入代码来“修复”引用。 这意味着复制结构不再是一个非常便宜的操作,只是移动一些位。 这甚至可能意味着这样的代码是昂贵的,这取决于假设的优化器是多么好:
let a = Object::new(); let b = a; let c = b;
而不是强制这种情况发生在每一个动作中,程序员可以选择何时发生这种情况,通过创建仅在您调用它们时才会使用相应引用的方法。
有一个具体的例子,你可以创建一个引用自己的类型。 您需要使用像
Option
这样的东西来做到两步:#[derive(Debug)] struct WhatAboutThis<'a> { name: String, nickname: Option<&'a str>, } fn main() { let mut tricky = WhatAboutThis { name: "Annabelle".to_string(), nickname: None, }; tricky.nickname = Some(&tricky.name[..4]); println!("{:?}", tricky); }
这在某种意义上是有效的,但是创造的价值是非常有限的 - 它永远不会被移动。 值得注意的是,这意味着它不能从函数返回或者通过值被传递给任何东西。 构造函数显示与上述相同的生命周期问题:
fn creator<'a>() -> WhatAboutThis<'a> { // ... }
Let's look at a simple implementation of this:
struct Parent { count: u32, } struct Child<'a> { parent: &'a Parent, } struct Combined<'a> { parent: Parent, child: Child<'a>, } impl<'a> Combined<'a> { fn new() -> Self { let parent = Parent { count: 42 }; let child = Child { parent: &parent }; Combined { parent, child } } } fn main() {}
This will fail with the error:
error[E0515]: cannot return value referencing local variable `parent` --> src/main.rs:19:9 | 17 | let child = Child { parent: &parent }; | ------- `parent` is borrowed here 18 | 19 | Combined { parent, child } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function error[E0505]: cannot move out of `parent` because it is borrowed --> src/main.rs:19:20 | 14 | impl<'a> Combined<'a> { | -- lifetime `'a` defined here ... 17 | let child = Child { parent: &parent }; | ------- borrow of `parent` occurs here 18 | 19 | Combined { parent, child } | -----------^^^^^^--------- | | | | | move out of `parent` occurs here | returning this value requires that `parent` is borrowed for `'a`
To completely understand this error, you have to think about how the values are represented in memory and what happens when you move those values. Let's annotate
Combined::new
with some hypothetical memory addresses that show where values are located:let parent = Parent { count: 42 }; // `parent` lives at address 0x1000 and takes up 4 bytes // The value of `parent` is 42 let child = Child { parent: &parent }; // `child` lives at address 0x1010 and takes up 4 bytes // The value of `child` is 0x1000 Combined { parent, child } // The return value lives at address 0x2000 and takes up 8 bytes // `parent` is moved to 0x2000 // `child` is ... ?
What should happen to
child
? If the value was just moved likeparent
was, then it would refer to memory that no longer is guaranteed to have a valid value in it. Any other piece of code is allowed to store values at memory address 0x1000. Accessing that memory assuming it was an integer could lead to crashes and/or security bugs, and is one of the main categories of errors that Rust prevents.This is exactly the problem that lifetimes prevent. A lifetime is a bit of metadata that allows you and the compiler to know how long a value will be valid at its current memory location. That's an important distinction, as it's a common mistake Rust newcomers make. Rust lifetimes are not the time period between when an object is created and when it is destroyed!
As an analogy, think of it this way: During a person's life, they will reside in many different locations, each with a distinct address. A Rust lifetime is concerned with the address you currently reside at, not about whenever you will die in the future (although dying also changes your address). Every time you move it's relevant because your address is no longer valid.
It's also important to note that lifetimes do not change your code; your code controls the lifetimes, your lifetimes don't control the code. The pithy saying is "lifetimes are descriptive, not prescriptive".
Let's annotate
Combined::new
with some line numbers which we will use to highlight lifetimes:{ // 0 let parent = Parent { count: 42 }; // 1 let child = Child { parent: &parent }; // 2 // 3 Combined { parent, child } // 4 } // 5
The concrete lifetime of
parent
is from 1 to 4, inclusive (which I'll represent as[1,4]
). The concrete lifetime ofchild
is[2,4]
, and the concrete lifetime of the return value is[4,5]
. It's possible to have concrete lifetimes that start at zero - that would represent the lifetime of a parameter to a function or something that existed outside of the block.Note that the lifetime of
child
itself is[2,4]
, but that it refers to a value with a lifetime of[1,4]
. This is fine as long as the referring value becomes invalid before the referred-to value does. The problem occurs when we try to returnchild
from the block. This would "over-extend" the lifetime beyond its natural length.This new knowledge should explain the first two examples. The third one requires looking at the implementation of
Parent::child
. Chances are, it will look something like this:impl Parent { fn child(&self) -> Child { /* ... */ } }
This uses lifetime elision to avoid writing explicit generic lifetime parameters. It is equivalent to:
impl Parent { fn child<'a>(&'a self) -> Child<'a> { /* ... */ } }
In both cases, the method says that a
Child
structure will be returned that has been parameterized with the concrete lifetime ofself
. Said another way, theChild
instance contains a reference to theParent
that created it, and thus cannot live longer than thatParent
instance.This also lets us recognize that something is really wrong with our creation function:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
Although you are more likely to see this written in a different form:
impl<'a> Combined<'a> { fn new() -> Combined<'a> { /* ... */ } }
In both cases, there is no lifetime parameter being provided via an argument. This means that the lifetime that
Combined
will be parameterized with isn't constrained by anything - it can be whatever the caller wants it to be. This is nonsensical, because the caller could specify the'static
lifetime and there's no way to meet that condition.How do I fix it?
The easiest and most recommended solution is to not attempt to put these items in the same structure together. By doing this, your structure nesting will mimic the lifetimes of your code. Place types that own data into a structure together and then provide methods that allow you to get references or objects containing references as needed.
There is a special case where the lifetime tracking is overzealous: when you have something placed on the heap. This occurs when you use a
Box<T>
, for example. In this case, the structure that is moved contains a pointer into the heap. The pointed-at value will remain stable, but the address of the pointer itself will move. In practice, this doesn't matter, as you always follow the pointer.The rental crate or the owning_ref crate are ways of representing this case, but they require that the base address never move. This rules out mutating vectors, which may cause a reallocation and a move of the heap-allocated values.
Examples of problems solved with Rental:
- Is there an owned version of String::chars?
- Returning a RWLockReadGuard independently from a method
- How can I return an iterator over a locked struct member in Rust?
- How to return a reference to a sub-value of a value that is under a mutex?
- How do I store a result using Serde Zero-copy deserialization of a Futures-enabled Hyper Chunk?
- How to store a reference without having to deal with lifetimes?
In other cases, you may wish to move to some type of reference-counting, such as by using
Rc
orArc
.More information
After moving
parent
into the struct, why is the compiler not able to get a new reference toparent
and assign it tochild
in the struct?While it is theoretically possible to do this, doing so would introduce a large amount of complexity and overhead. Every time that the object is moved, the compiler would need to insert code to "fix up" the reference. This would mean that copying a struct is no longer a very cheap operation that just moves some bits around. It could even mean that code like this is expensive, depending on how good a hypothetical optimizer would be:
let a = Object::new(); let b = a; let c = b;
Instead of forcing this to happen for every move, the programmer gets to choose when this will happen by creating methods that will take the appropriate references only when you call them.
A type with a reference to itself
There's one specific case where you can create a type with a reference to itself. You need to use something like
Option
to make it in two steps though:#[derive(Debug)] struct WhatAboutThis<'a> { name: String, nickname: Option<&'a str>, } fn main() { let mut tricky = WhatAboutThis { name: "Annabelle".to_string(), nickname: None, }; tricky.nickname = Some(&tricky.name[..4]); println!("{:?}", tricky); }
This does work, in some sense, but the created value is highly restricted - it can never be moved. Notably, this means it cannot be returned from a function or passed by-value to anything. A constructor function shows the same problem with the lifetimes as above:
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
What about
Pin
?
Pin
, stabilized in Rust 1.33, has this in the module documentation:A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
It's important to note that "self-referential" doesn't necessarily mean using a reference. Indeed, the example of a self-referential struct specifically says (emphasis mine):
We cannot inform the compiler about that with a normal reference, since this pattern cannot be described with the usual borrowing rules. Instead we use a raw pointer, though one which is known to not be null, since we know it's pointing at the string.
The ability to use a raw pointer for this behavior has existed since Rust 1.0. Indeed, owning-ref and rental use raw pointers under the hood.
The only thing that
Pin
adds to the table is a common way to state that a given value is guaranteed to not move.See also:
相关问答
更多-
TCP/IP模型是一个________。[2023-10-02]
a -
下列中不属于面向对象的编程语言的是?[2022-05-30]
a -
为什么我不能在同一个结构体中存储值和引用该值?(Why can't I store a value and a reference to that value in the same struct?)[2022-03-26]
我们来看一下这个简单的实现 : struct Parent { count: u32, } struct Child<'a> { parent: &'a Parent, } struct Combined<'a> { parent: Parent, child: Child<'a>, } impl<'a> Combined<'a> { fn new() -> Self { let p = Parent { count: 42 }; ... -
如果您的代码没有太多变化,我已经在这里修复了两件事。 1)您正在尝试打印从地址45开始的字符串。 这是一个问题,显然它会打印垃圾。 2)你可以按照原样向前传递指针。 #include
typedef struct _buf { void *ptr; size_t len; } buf; int func3(buf *x){ x->ptr = (void*)"hello"; printf("func3 %s \n", (char *)x->ptr); retur ... -
将struct存储在数组中(Store struct in array)[2021-08-09]
您需要将内存块转换为xMetaData : *(xMetaData *) (&memory[0]) = xInitialData; 如果您正在使用结构来进行此类操作,您还应该知道结构填充(例如,确保ALLOCATE_SIZE使用sizeof(xMetaData)而不是硬编码长度,并确保始终使用结构访问内存。 ) You need to cast the block of memory to xMetaData: *(xMetaData *) (&memory[0]) = xInitialData; Yo ... -
有人告诉我,数组是通过引用传递的。 那不是真的。 根本没有传递数组,没有函数可以将数组作为参数。 当你声明了一个函数 void foo(int bar[]); foo采用的参数类型实际上是int * ,当你调用它时 int arr[23]; /* fill arr with meaningful values */ foo(arr); 指向arr的第一个元素的指针按值传递。 所有函数参数都通过C中的值传递,没有异常。 因此,当您将struct Person传递给函数时,它会通过值传递,因此会复制stru ...
-
免责声明:在这个答案中,我会假设您不愿意使用enum因为您希望Employee开放。 这个问题出现在使用动态多态的每种语言中,传统的答案是访问者模式 。 但是,由于它引入了依赖性,它并不完全理想,因此如果需要,您可以使用非循环访问者模式 ; 但是我建议你先从裸骨访客开始,然后再深入研究。 trait EmployeeVisitor { fn visit_employee(&self, e: &Employee); fn visit_human(&self, h: &Human); } tr ...
-
是否可以从List
访问结构的引用以进行更改?(Is it possible to access a reference of a struct from a List [2022-04-12]to make changes?) 不,为了能够做到这一点,你需要引用List / IList不提供的内部数组元素。 如果必须,您可以使用不安全的代码和数组。 No, to be able to do it you need reference to element of inner array which is not provided by List/IList. You can do that with unsafe code and arrays if you have to. -
我怎样才能确保结构中只有一个成员一次占用内存中的空间? 如果编译器(C ++ 17)支持,你可以使用std::any或std::variant ,或者你可以为你的类型 template
struct wrap { T elem; }; How can I ensure only one member of a struct takes up space in the memory at a time? You can use a std::any, or std::vari ... -
好的, Instances是一个静态变量 - 所以它将在堆上。 同样,数组本身也是一个引用类型,所以这将会堆积如山。 只有uid变量实际上是特定TestStruct的值的一部分,并且这将取决于上下文在堆上或堆栈上。 尽管这只是一个实现细节 ......你究竟想要实现什么? Okay, so Instances is a static variable - so that will be on the heap. Likewise the array itself is a reference type, s ...