17

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).

top 15 comments
sorted by: hot top new old
[-] ExperimentalGuy@programming.dev 6 points 2 days 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.

[-] vrek@programming.dev 2 points 2 days ago

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

[-] tatterdemalion@programming.dev 1 points 2 days ago

Very true, and GDB will tell you this.

[-] Lee@retrolemmy.com 3 points 2 days 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. ;)

[-] vrek@programming.dev 1 points 2 days 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.

[-] Lee@retrolemmy.com 2 points 2 days 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.

[-] vrek@programming.dev 2 points 2 days 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.

[-] Lee@retrolemmy.com 2 points 1 day ago* (last edited 1 day 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.

[-] vrek@programming.dev 1 points 1 day ago

Wow, ok I guess I have a lot more to learn. I get the general concept of pointers/references and stack/heap. I kinda partially understand reading asm but didn't know about the rsp and rsb special memory locations, more familiar with like rax or eax but those are just general purpose registries, and that's mostly just from playing with https://godbolt.org/ if you're not familiar it's a website to convert code(many supported languages) to asm with several compilers and options similar to your g++ command but web based so I don't know how it would handle low level memory checking like this(also can't execute code, just display the asm code).

[-] Lee@retrolemmy.com 1 points 1 day ago* (last edited 1 day ago)

Wow, ok I guess I have a lot more to learn.

I think it depends on how low level you want to go. The benefit of using these kind of languages is that they abstract away the hardware so that you don't have to worry about the details. Granted details can matter when you're trying to push performance to the limits or be incredibly efficient, but that's so rare for people to do because it can be very time consuming and on modern CPUs, a compiler can often do optimizations that are better than a human would, especially when taking in to account advanced CPU features. With Out of Order Execution, branch prediction, pipeline flushes, and so on, trying to manually optimize stuff at a low level can result in things actually being less efficient (on a modern CPU) even though it would have been more efficient on a 1980s CPU (granted, my current hobby project is with a CPU popular in the 1980s -- Motorola 68K).

What I think is related to stack vs heap that's actually worth learning:

  1. new/malloc are slow
  2. pointers (don't worry about stack vs heap for this)
  3. variable scope/lifetime -- memory allocated via new/malloc stays allocated until it is deallocated with delete/free. Messing this up can cause memory leaks or other types of bugs and from what I understand, something that Rust is supposed to do better (but I've not looked at Rust, so don't really know for sure)

I think those are necessary. If you're wanting to go a little deeper on memory stuff, but not quite down to assembly, you could learn about memory pages/page tables/how operating systems handle memory allocations/virtual memory. Those topics are not C or C++, but more general computer/OS.

[-] vrek@programming.dev 1 points 1 day ago

Well my current project is not really performance critical within reason. Basically trying to write a program to handle a bunch of statistics for manufacturing. If you know what minitab is, kinda similar but multi-platform(gui is qt based) and removes some pain points. So for example if your company makes cuts wooden planks and you measure them, you can track those measurements and get a warning if something is going on before you make bad product(maybe something is wrong with machine, maybe it's high humidity causing wood to swell, maybe it's a new operator who needs training... Figuring that out is the engineers job)- . An example of additional feature is ability to generate a gage r&r plan sheet for a new measurement system. A gage r&r is often 10 samples, tested 3 times each by 3 different operators to see how repeatable and reproducible(hence r&r) your measurement system is. But you want the order randomized to eliminate hidden changes(for example humidity decreasing from morning to afternoon) how ever it's often easier to "borrow" an operator for an 2 hours then another then another than to borrow 3 operators for 6 hours. Minitab has the ability to generate a run order to do this but it randomizes both samples and operator meaning you need 3 operators for 6 hours. People got around this by faking their "randomness", one person I knew even tried to do this with a d10 die. This tends to not be real random and is easy to make a mistake(for example person with d10 didn't notice they rolled a 7 twice on the third run of the second operator). We had to investigate this failure, write a report of our findings and repeat the who test. I want to give an option to randomize samples and operators or only operators.

All the math for this stuff is pretty well known, proven and documented. The only performance I'm concerned with is with large datasets. For example we had one process that produced 5-7k data points per day, they had data going back till like 2010. The file was like 2 gig and took a good 5 minutes to open. I want to create a new file for each process, each year(time range may be adjustable). Then you have "product" file you open, then you select which processes you are interested in and the time frame you are interested in. All your data is then loaded(the product file effectively just has links to all associated process files for each year with data) and graphs all updated based only on the data you selected(my company reviewed data for previous month but had to keep records for up to 100 years for legal reasons). That should cut out most my performance concerns. But 7k data points on one process per day over 6 months is 1,281,000 data points so still have to be a little concerned.

I'm also unemployed so I guess that's why I'm ranting uncontrollably too. 😋

[-] tatterdemalion@programming.dev 4 points 3 days ago

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

info variables
info locals
info args
info address
info proc mappings
[-] vrek@programming.dev 3 points 3 days ago

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

[-] tatterdemalion@programming.dev 3 points 3 days 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.

[-] vrek@programming.dev 2 points 3 days 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.

this post was submitted on 01 Sep 2026
17 points (100.0% liked)

Learn Programming

2231 readers
53 users here now

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

founded 3 years ago
MODERATORS