Is there a way to see if some variable is stored on the stack or the heap?

a month ago by vrek to c/learn_programming

I believe I have a good understanding of what is stored on the stack(for example primitive data types) and what's stored on the heap(for example an instance of a class. But is there anyway to verify this for educational/verification of my expectations reasons?

It can be in a debugger(gdb or rad debugger or worst case vs) or directly code based(thinking something like gettype(variable) but like isstackstored(variable).

ExperimentalGuy 6 points a month ago

Another commenter said that checking if the variable's memory address is in the stack region or heap region, and that is true, but I also wanted to mention that sometimes stack variables live short enough that they're simply stored in a register instead of actually put in memory.

path: 0 25602490, hotness: undefined, score: 6, children: 2
vrek 3 points a month ago

Sometimes I swear my brain does the same thing(short lived things like people names, no need to put that in memory) 🤣

path: 0 25602490 25609279, hotness: undefined, score: 3, children: 0
tatterdemalion 2 points a month ago

Very true, and GDB will tell you this.

path: 0 25602490 25608653, hotness: undefined, score: 2, children: 0
tatterdemalion 5 points a month ago

I recommend checking out the documentation for the following GDB commands:

info variables
info locals
info args
info address
info proc mappings
path: 0 25596109, hotness: undefined, score: 5, children: 3
vrek 3 points a month ago

Most of those will give me a memory address but is there a way to know if that address is stack or heap?

path: 0 25596109 25596202, hotness: undefined, score: 3, children: 2
tatterdemalion 3 points a month ago

info proc mappings should show you the memory regions mapped for stack and heap, then you can compare the address of your variable to those.

path: 0 25596109 25596202 25597226, hotness: undefined, score: 3, children: 1
vrek 2 points a month ago

I don't see that in the sourceware or gnu documentation but I may be missing it (gdb is incredibly complex and documentation in long and detailed). It's 1am here so investigation into this will have to wait till tomorrow.

path: 0 25596109 25596202 25597226 25597289, hotness: undefined, score: 2, children: 0
Lee 3 points a month ago

Is this a general curiosity looking for a rule of thumb that mostly works or do you need to know with certainty? What language?

A simple rule of thumb for C/C++ could be that malloc/new/dynamically allocated variables are in heap and variables whose size is known at compile time and have local scope are stack. This is not strictly true, but it's simple and mostly true.

Depending on how much detail/special cases you want to get in to, it's possible for a variable to not be in or referencing/pointing to either the stack or heap. I'll leave scenarios where this may occur as exercise for the reader. ;)

path: 0 25602219, hotness: undefined, score: 3, children: 7
vrek 1 point a month ago

Mostly c/c++/c# but also know some python and rust.

This is just for self educarion/exploration. Nothing critical.

Even you said your rule of thumb is "not strictly true" which is my point. Is there some way to check those cases? Like I can verify the type of a variable returned by a function. Like if I get people's ages from a database but some one stored them in a text field instead of an numeric field. When I get the data back I can verify the type before I try to say average the ages.

A simple idea of what I'm thinking is let's say you're doing very precise calculations which need to very accurate so a co-worker makes a custom double class and returns that from their special add function. Since it's an instance of a custom class it's likely stored on the heap. Naming is hard though so your coworker named it Double instead of double. So you look at your code and think Double is a primitive so should be on the stack but it's actually on the heap. Even simpler example, primitive types are stack based except for string which is heap based. These are easy mistakes to make but could have performance and memory implications. So a way of verifying assumptions would be useful.

path: 0 25602219 25609128, hotness: undefined, score: 1, children: 6
Lee 2 points a month ago

I'm not sure your level of knowledge on computer architecture, so idk how deep to go on some things. There's also an insane amount of pedantry that you can get in to with these kinds of language/architecture/platform specific things. I'm going to avoid the pedantry, mostly because I'll probably get something wrong, but also because it's probably not that helpful to you. I also used to work professionally with C and later C++, but haven't done so for many years. I do currently use C for a memory constrained hobby project (neogeo: 8MB ROM, 64KB RAM) and closely pay attention to my memory usage via gcc compiler flags. I also have no clue about Rust or Python.

So mostly avoiding pedantry, on what you're likely working on (PC like platforms, common operating systems, common libc implementations), the stack and heap are both RAM, they're just a different addresses and there's not a performance implication to this (at least not because of where the variables are stored). The performance issue is the allocation and freeing process for heap is far more complicated whereas stack can be simply a matter of adding/subtracting the stack pointer and space can be allocated (or freed) for multiple variables simultaneously... Like if you have int x, y, z, the memory for all 3 variables can be allocated/freed by simply moving the stack pointer 12 bytes (assuming 4 byte ints * 3 variables), but this only works if the scope of the variables is limited to the function or code block. If the lifetime needs to extend beyond that, then it needs to be allocated to the heap. How do you do that? new/malloc. So for the most part you can just assume that if a variable has been allocated with new or malloc, then it's in heap, but if we're pedantic, this isn't defined by the language, it's implementation specific. It's just that you're most likely to be working on systems where what I said is true.

Using your custom double as an example. It's not correct to assume primitives are allocated on stack and custom types are heap. If you do: int *x = malloc(sizeof(int)); this is heap and similarly if you do BetterDouble db; then this is stack.

I'd say it doesn't actually matter in most cases and it's not worth the effort. Avoid using new/malloc unless it's necessary (like the size of the variable is only known at runtime or you need the data to exist beyond the lifetime of the function) because that is going to do a heap allocation. But what if you really want to know without looking for new/malloc or just to be sure the compiler didn't insert them for some reason? Well, there's various compiler flags that will give you information about how much memory is allocated for stack and some other data, but not necessarily on a variable by variable basis (I use -print-memory-usage). At least I don't know of any way to get variable level data out of gcc. If there is, I'd like to know for my own purposes.

If you need to know at runtime, like you want your program to do something differently if a variable points to an address in heap vs stack, there's not a language (C or C++) defined way to do this that I'm aware of, so you have to resort to platform specific approaches to get the stack pointer / address of stack and then compare it to your variable's address. If you just want to sort of manually inspect, as another commenter said, there's gdb info proc mappings, but that may not be very convenient depending on what you're doing.

So with all of that, if you really want to know and you're on a machine that has pthread_attr_getstack(), then use it compare your variable address to the stack address. Probably the easiest way to "be sure", but really I'd personally just assume stack unless I used new/malloc or I was really trying to micro optimize something.

path: 0 25602219 25609128 25615244, hotness: undefined, score: 2, children: 5
vrek 2 points a month ago

First off thank you for the in depth response. I running on Debian Linux with mostly editing c/c++ in clion by idea. As for my experience, no college but many years of professionally messing around with "programming" but not in the typical sense. I did a lot of g-code which is used on cnc machines and pcdmis which is used on CMMs. I also did some "programming" in excel and power bi and sql. I had some exposure to c# and was "trained" in python but that training was pathetic(3 month course and about 1/2 the class admitted during final presentation that they wrote their entire final project as 1 function because they couldn't figure out to to pass variables in to functions or get resulting variables out... Every student still passed).

Most of this was just for confirming what I hear and learning more about how it works. For example I've see many places claim that primitives are always on the stack, I never considered an int * =malloc(sizeof(int));. But that makes sense that it would be heap allocated and I can see some uses.

I realize a lot of this doesn't really matter, especially with modern computers but you are I think the third person to say "that's not technically always" or something with similar meaning. Yes I'm sure I can read books and papers and learn the edge cases but I'm still trusting that author. I was curious if I could prove it, or disapprove it on any system.

My only way of verifying this that I can find is verifying copy. Basically make a variable and set it to something, make second variable and set it equal to first, modify second variable, test if variable 1 == variable 2. If yes, it's heap allocated, if not it's stack allocated. My understanding is no matter what you create, a value is created on the stack. The difference is if stack allocated the data is stored directly, if heap the data on the stack is a pointer to an address on the heap. As such if you copy the variable, your copying the value in the stack. If it's stack allocated the data is literally copied and you have the same data in two places, when you modify one the other is left alone. If it's heap allocated, the address is copied into a new spot on the stack, but since both point to same address if you modify one you modify both.

Yeah, none of this will likely matter in any job or any project I do but if I was told something I want to KNOW it's true. Plus if I'm doing something special, it would be nice to have confirmation. Like if I'm on a arduino or es32 do they use the same memory configuration? What about a fanuc cnc controller? A cognex aoi camera using "spreadsheet" mode(that is probably no since it's literally programmed via a spreadsheet) ? Yes you MAY be able to tell me but even then I have to trust that you know it, it wouldn't be the first time Ive seen manufacturer employees quote incorrect information to me.

path: 0 25602219 25609128 25615244 25616055, hotness: undefined, score: 2, children: 4
Lee 2 points a month ago

but I’m still trusting that author. I was curious if I could prove it, or disapprove it on any system.

I understand and admire this. Don't trust me. Granted, at some point you're going to have to trust someone. I'm going to assume that you trust that a stack allocated variable will be referenced by way of the stack pointer. On x86, that's a register called "RSP" and I'll show you exactly how you can see for yourself what gcc/g++ is outputting.

Here's some code: test.cpp

#include <iostream>

using namespace std;

int main(void) {
  int x;
  x = 42;
  cout << "x = " << x;
  return 0;
}

You can have g++ convert this to assembly with comments with: g++ -S -fverbose-asm test.s test.cpp Here's a portion of the output on my system:

main:
.LFB1988:
    .cfi_startproc
    endbr64	
    pushq	%rbp	#
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq	%rsp, %rbp	#,
    .cfi_def_cfa_register 6
    subq	$16, %rsp	#,
# test.cpp:4:   int x = 42;
    movl	$42, -4(%rbp)	#, x
# test.cpp:5:   std::cout << "x = 42";

The piece I'll point to is the references to the x variable:

# test.cpp:4:   int x = 42;
    movl	$42, -4(%rbp)	#, x

This is moving the constant 42 ($42) in to the memory location -4(%rbp). This is a way of basically saying "the value stored in %rbp minus 4". I did say %rsp is the stack pointer, so why is it referencing %rbp? Well, see a couple lines above: movq %rsp, %rbp #, (it's copying the value of %rsp in to %rbp and then referencing via %rbp, so %rbp is identical to what %rsp was before the extra variable allocations at the time of the assignment of x = 42). Also note in later examples how the offset (-4 in this example) to %rbp (and %rsp) changes as we add more variables.

OK so how can we actually see the value of %rsp (or %rbp)? Well with some inline assembler to copy the value of %rsp in to a variable and then print it. So here's an example printing x, pStack, and their addresses:

#include <iostream>

using namespace std;

int main(void) {
  int x;
  x = 42;
  int *pStack;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  __asm__("movq %%rsp, %0;" : "=m" (pStack));
  cout << "pStack = " << pStack << endl;
  cout << "&pStack = " << &pStack << endl;
  return 0;
}

On my system, here's the output I get when I run it:

$ ./test2
x = 42
&x = 0x7ffcbd78a0bc
pStack = 0x7ffcbd78a0b0
&pStack = 0x7ffcbd78a0c0

So you can see pStack is address 0x7ffcbd78a0b0 and x is address 0x7ffcbd78a0bc. Try this with more complex types (struct, class) and see that the variable addresses are lower than the stack pointer.

Now for a heap allocation:

test3.cpp
#include <iostream>

using namespace std;

int main(void) {
  int *x = new int;
  *x = 42;
  int *pStack;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  __asm__("movq %%rsp, %0;" : "=m" (pStack));
  cout << "pStack = " << pStack << endl;
  cout << "&pStack = " << &pStack << endl;
  return 0;
}

When I run it, I get:

$ ./test3
x = 0x5ca98bc312b0
&x = 0x7ffd768137c8
pStack = 0x7ffd768137c0
&pStack = 0x7ffd768137d0

So we can see that x (a pointer) is pointing to a drastically different memory address (it's the heap). So why is &x (address of x) the stack? Well because x itself is a stack variable, but it's a pointer that is pointing to heap allocated memory.

You can do this kind of test with your custom classes, fixed sized arrays like int x[10] or even with arrays of complex types BetterDouble bd[10] and so on until you're convinced.

My only way of verifying this that I can find is verifying copy. Basically make a variable and set it to something, make second variable and set it equal to first, modify second variable, test if variable 1 == variable 2. If yes, it’s heap allocated, if not it’s stack allocated. My understanding is no matter what you create, a value is created on the stack. The difference is if stack allocated the data is stored directly, if heap the data on the stack is a pointer to an address on the heap. As such if you copy the variable, your copying the value in the stack. If it’s stack allocated the data is literally copied and you have the same data in two places, when you modify one the other is left alone. If it’s heap allocated, the address is copied into a new spot on the stack, but since both point to same address if you modify one you modify both.

This may just be a terminology difference or I'm misreading, but I think you may be confusing pointers vs the data they point to and stack vs heap. The pointers and stack/heap are 2 separate, although somewhat related, concepts. What I mean is that the behavior you describe (modifying variable2 and having it impact variable1) is true if variable2 is a pointer to variable1 and you're modifying the data contained (what variable2 is pointing to), but that is independent of stack vs heap. Generally you reference heap allocated variables via pointers, but you can also reference stack allocated variables by pointer. Here's an example using only stack variables (and pointers).

#include <iostream>

using namespace std;

int main(void) {
  int x = 42;
  int *y = &x;
  int *pStack;
  __asm__("movq %%rsp, %0;" : "=m" (pStack));
  cout << "pStack = " << pStack << endl;
  cout << "&pStack = " << &pStack << endl;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  cout << "y = " << y << endl;
  cout << "*y = " << *y << endl;
  (*y)++;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  cout << "y = " << y << endl;
  cout << "*y = " << *y << endl;
  return 0;
}

Output:

$ ./test4
pStack = 0x7ffd87bbb440
&pStack = 0x7ffd87bbb448
x = 42
&x = 0x7ffd87bbb444
y = 0x7ffd87bbb444
*y = 42
x = 43
&x = 0x7ffd87bbb444
y = 0x7ffd87bbb444
*y = 43

Key points:

  1. address of both x and y are stack
  2. y is pointing to the address of x
  3. updating the data pointed to by y causes x to be updated (because y is pointing to the address of x)
  4. there's no heap references here

Another concept that is related to this is "pass by reference" vs "pass by copy", so you may want to read about that.

EDIT: fixed a typo (higher/lower with respect to stack memory address). I'll blame my rambling on being unemployed.

path: 0 25602219 25609128 25615244 25616055 25619789, hotness: undefined, score: 2, children: 3
learn_programming
learn_programming

@programming.dev

login for more options
2254
161
53

Posting Etiquette

  1. Ask the main part of your question in the title. This should be concise but informative.

  2. Provide everything up front. Don't make people fish for more details in the comments. Provide background information and examples.

  3. Be present for follow up questions. Don't ask for help and run away. Stick around to answer questions and provide more details.

  4. Ask about the problem you're trying to solve. Don't focus too much on debugging your exact solution, as you may be going down the wrong path. Include as much information as you can about what you ultimately are trying to achieve. See more on this here: https://xyproblem.info/

Icon base by Delapouite under CC BY 3.0 with modifications to add a gradient

go to feed...