Hi all,
How can I print out the memory address of a variable e.g. print out the memory address of x
in let x = 100;
rather than the value 100
?
Same question for array, struct variables.
Thank you,
Hi all,
How can I print out the memory address of a variable e.g. print out the memory address of x
in let x = 100;
rather than the value 100
?
Same question for array, struct variables.
Thank you,
fn main() {
let my_var = 100;
println!("{:#x}", (&my_var) as *const i32 as usize);
}
0x7fffe76ac864
Thank you very much, it really helps.
Btw, what if I want to print out the memory address of xref
in let xref = &x
rather than the value it points to?
Thanks,
You need to cast the reference to a raw pointer to do that. References print the value behind them.
use std::ptr::addr_of;
fn main() {
let num = 32;
println!("Address: {:?}", addr_of!(num));
let borrow = #
println!("Address: {:?}", addr_of!(borrow));
}
You can also use the {:p}
(pointer) format:
fn main() {
let x = 100;
let xref = &x;
println!("{:p}", &x);
println!("{:p}", &xref);
}
WARNING!
This is a very unreliable and misleading way of trying to figure out what Rust does.
Taking an address of a variable changes what the program is doing. It happens to be a very important side effect for the optimizer. It may prevent data from being in registers (variables can exist in registers, but registers don't have addresses). It can also make the optimizer pessimistically assume the address "escapes", and therefore the variable can't be optimized away. Just taking an address of a variable can make code behave differently than the same code that never saw the address.
Rust allows moves, so addresses of most things are temporary and meaningless. It can move variable to one location, let you take its address, and then move the variable to entirely different address. The value you'll print could be out of date and irrelevant. It may be reused for other variables or other data.
Is this true? I would think a variable's address stays constant throughout its life. Can you provide example code?
If you don't take its address, it could be optimized to be stored in a register. In that case, it has no address.
It also definitely can be moved. The program only has to behave as-if it doesn't move.
But that's true about everything -- everything can be changed in compiled code if it doesn't modify the behavior. That's like saying: u32
might have 64 bits (as long as the difference is not observable).
Sure, it might be compiled to something that moves in memory, but that's not in the semantics of Rust (as I understand), it's just a code transformation that might be performed. You seem to agree that as far as the semantics of the language is concerned, addresses are fixed.
It is absolutely possible for variables to change address, if they don't have their address taken. This is dictated by register pressure, and LLVM may decide to spill registers to stack and load them back many times throughout the function (e.g. dump variables to stack before a loop that doesn't use them, reload them from the stack after, and then dump (some of) them elsewhere next time registers are needed).
This is also why observing an address can have such a big impact. If the address is known to the program, it forces the place behind it to be more like the variable you expect, with a known address that must stay valid for the lifetime of the variable. But if the address never leaks, the variable can be treated as a simple anonymous value, with no obligation to keep it at all, and can be merged into other expressions or discarded before its scope ends.
Even struct fields are not going to stay in the same place as the struct. LLVM has a pass that breaks up struct fields into individual variables, and then decides individually whether to keep them in registers or memory. So something that looks like a single struct variable in the code, may end up being multiple variables, which then may be folded into other expressions, and their scope moved around as the code is reordered.
And there's even a funny case of WONTFIX UB in LLVM. It has both:
false
without checking,Obviously, but that just follows from the general rule that the compiler may translate your Rust code into any machine code it wants with the same observed behavior. That has nothing to do with some special rule that says:
I believe Rust doesn't have any such rule that allows extra moves at arbitrary times, moves only happen explicitly (but I may be wrong, which is why I asked).
I think the point here is the indirect one that you can't really do anything useful by looking at the variables this way, since by looking you've perturbed what happens.
So while sure, it'll give some addresses, you can't make any useful decisions with those addresses, since any kind of "why does it _______?" questions will probably end up answered with "well it wouldn't if you weren't printing it".
I disagree. It depends on what kind of questions you want answered. You will not learn about how the code is optimized exactly in many cases, but that's not the only thing you might want to investigate.
For example, by looking at addresses you will learn that:
x
and xref
are not aliases, they are different variablesVec
contents don't move when you move a Vec
to a different variableYes, there is the important caveat that optimization might change what happens under the hood under the as-if rule when you're not looking. But if you're not specifically investigating optimization that's OK because the resulting observed behavior is the same anyway. It's still useful information to see what happens "in theory".
If you're interested in figuring out "what Rust does" with your code, you're a lot better off sticking it into https://rust.godbolt.org/ and using Matt Godbolt's Compiler Explorer to look at LLVM IR, machine code etc. You can even set up a view with llvm-mca
and the optimization pipeline exposed to get a sense of what the compiler finally outputs in terms of CPU resources, and how the LLVM IR that rustc
translated your program into is turned into machine code.
I like cargo asm
even more than godbolt, because it can show code of any project with dependencies, without need to reproduce a minimal example online.
And I think it's important to note that Rust is designed to work with an optimizer. The unoptimized code is going to have many function calls, wrapper types, temporary values, and copies that are not meant to be there. The unfortunate flip side of that is that it requires looking at the actual assembly (or final LLVM IR) to really understand what Rust is supposed to be doing.
The question whether &i32
and i32
are different depends on what abstraction level you ask. In Rust's semantics they're different just because Rust says so, and their addresses don't play a role here (that's an implementation detail). OTOH in the implementation, the difference between &i32
and i32
may not exist. The compiler may choose to never store i32
in memory and ignore the reference entirely, or may choose to dereference a &i32
early and treat it as i32
(it easily can, since it's immutable).
All this talk about assembly, optimizations, etc, is completely irrelevant to the original question. It was asked by a beginner that wanted to see that there is a difference between an i32
variable and another &i32
variable that points to it.
They didn't ask to see assembly or how printing something affects optimizations. These are different, advanced topics.
It's not really true that taking an address of something "changes what the program is doing". It just prints something in addition to what the program is doing.
It (like many things) affects what kind of optimizations will be performed, but generally optimizations do not, by design, affect any functionality, they only affect performance. Yes, if you never look at the address, a variable might be optimized to be stored in register, or to not be stored anywhere, but that doesn't actually affect anything (except performance) since you're not printing the address in that scenario (which is why the optimizer can do this).
Hi All,
Thank you all very much for both solutions and extended conversation through it I learn a lot.
core::ptr::addr_of!
because some of my application don't use stdlib
.This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.