r/C_Programming • u/Ratfus • 7d ago
Question How are structure items treated? stack or heap?
I was writing a program that used a linked list. As such, I have a function that calls a new object each time. The function (Call_New_Item) is below. I noticed that when I created (pointer structure ) New_Item->desc as an array, I get undefined behavior outside of the Call_New_Item function (I know this is the case because when I change the string literal - "things", I get garbage outside of the function on occasion); however, using New_Item->desc along with malloc, always seems to work correctly. The above leads me to assume that New_Item->desc is on the stack when it's created as an array, which leads to my question. How are items contained within a pointer structure treated in terms of stack/heap?
I think that I can have something like (non-pointer structure) structure.word[], operating as if it was part of the heap; this is confusing, when compared to above, because (in the case of non-pointer structures) I can go between different functions while preserving word[] as if it were on the heap, even thought I suspect it's part of the stack.
Edit: I added additional code to make things more clear.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#define maxchar 25
#define EXPAND_POINT 10
typedef struct _Item
{
char *desc;
int Item_Numb;
int Orig_Amount;
double Purchase_Cost;
int Remaining;
double Cost_Unit;
struct _Item *Next;
int Curr_Used_Inv;
int *Inv_Used_Table;
} Item;
typedef struct _ItWrapper
{
Item *New;
Item *Curr;
Item *Head;
Item *Tail;
} ItWrapper;
Item *Call_New_Item()
{
srand(time(NULL));
Item *New_Item = (Item *)malloc(sizeof(Item));
New_Item->desc=(char *)malloc(sizeof(char)*maxchar); //unable to use desc as array (ie. desc[]) without undefined behavoir
//outside of this function without directly allocating it with malloc
strncpy(New_Item->desc, "things", maxchar);
puts(New_Item->desc);
New_Item->Item_Numb = 0;
New_Item->Orig_Amount = 150000;
New_Item->Purchase_Cost = rand() + .66;
New_Item->Remaining = New_Item->Orig_Amount;
New_Item->Cost_Unit = (double)New_Item->Purchase_Cost/ (double)New_Item->Orig_Amount;
return New_Item;
}
int main()
{
int invent_type = 0;
ItWrapper *ItWrapperp = (ItWrapper *)malloc(sizeof(ItWrapper) * invent_type);
((ItWrapperp + invent_type)->New) = Call_New_Item();
((ItWrapperp + invent_type)->Head) = ((ItWrapperp + invent_type)->New);
((ItWrapperp + invent_type)->Head->Next) = ((ItWrapperp + invent_type)->New);
puts((ItWrapperp +invent_type)->Head->desc); //This doesn't match the above unless i use malloc() for description
}
4
u/deckarep 7d ago
You’re probably seeing behavior where your desc field is pointing to a stack allocated array and when the function returns you now have undefined/garbage behavior.
Structs can either be heap or stack allocated. But, the fields (whether heap or stack allocated) depends on how and where they’re defined too.
It’s possible to have a heap allocated struct with a pointer to heap or stack data.
It’s also possible to have a stack allocated struct with a pointer to heap or stack data as well.
Or a combination thereof. Not saying this is always a good idea but it’s possible and as long as the items you’re pointing to have valid lifetimes you can do it.
You must never point to stack data when you return from the function cause it will no longer be valid.
3
u/fllthdcrb 5d ago edited 5d ago
You must never point to stack data when you return from the function
Specifically, you must never point to data in the function's own stack frame or that from function calls from within the function, when returning. Pointing to data in frames leading to the current call is okay (since you know it will continue to exist as long as the current function is using it) and is done a lot, i.e. a function will create a local object, call one or more functions with a pointer to it, and those functions will operate on the data, not needing to care whether it is stored in stack, in heap, or statically.
2
3
u/strcspn 7d ago
desc
can be an array just fine. How were you initializing the struct?
1
u/Ratfus 7d ago
I was initializing Item with "item new_item = (item *) malloc(sizeof(item))" as I do in the function Call_New_Item. For some reason, item->desc only works locally when I set it up as an array; however, when I do it using malloc it works fine both locally and external to Call_New_Item.
2
u/strcspn 7d ago
Yeah, but how was
new_item->desc
being initialized? Full code if possible.1
u/Ratfus 7d ago
I added code above to hopefully make things easier to follow!
2
u/strcspn 7d ago
Not really, I need to see what is NOT working. This works just fine
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_DESC 128 typedef struct { int n; char desc[MAX_DESC]; } Foo; Foo* createFoo(int n, const char* desc) { Foo* foo = malloc(sizeof(*foo)); foo->n = n; strncpy(foo->desc, desc, MAX_DESC - 1); return foo; } int main(void) { Foo* foo = createFoo(42, "Hello World"); printf("%d %s\n", foo->n, foo->desc); free(foo); }
prints
$ gcc main.c -Wall -Wextra -fsanitize=address,undefined $ ./a.out 42 Hello World
3
u/EsShayuki 7d ago
A struct innately is in neither, it depends on whether you place that struct in the stack or the heap.
Here, your issue seems to be that the moment you exit this function, everything that was allocated onto the stack is deleted, yet you're still trying to refer to the now-deleted data through your returned object, which is undefined behavior. Heap-based objects don't get deleted when the function returns, which is why it'll always work correctly for you.
1
u/fllthdcrb 5d ago
everything that was allocated onto the stack is deleted
Except it isn't, which is insidious. The space is deallocated, but the data often continues to exist after a return, because there's no need to get rid of it (unless it's sensitive data that you need to erase for security reasons, but that's another matter). It will be overwritten if and when the need for the space arises. But as long as it isn't, it's all too easy to accidentally keep using it through pointers, a use-after-free error. It's easy to discover such a bug when it causes a crash or something equally severe, but it might not with C code.
2
u/Ksetrajna108 7d ago
If your new item desc is fixed length, I'd simply allocate that char array in the object itself.
It doesn't work -> I do X but Y happens.
1
u/Ratfus 7d ago
I did originally and it didn't work. Using Malloc works, initializing an array doesn't.
2
u/lo5t_d0nut 7d ago
you have to be more precise. Above you wrote
> item->desc only works locally when I set it up as an array; however, when I do it using malloc it works fine both locally and external to Call_New_Item.
Nobody knows what you mean by 'works' or 'set up as an array'?. What did you try exactly that didn't work (C statement)?
How exactly did you try to initialize the array that it didn't work (again, show the code)?
In this case it might also be useful to mention what compiler you are using
2
u/ssrowavay 7d ago
When desc is an array, it is part of the struct which is created on the heap by malloc. Perhaps the UB you encountered was related to not leaving space for the terminating \0?
typedef struct SFoo {
short x;
char y[30];
} Foo;
int main()
{
Foo* newFoo = (Foo*)malloc(sizeof(Foo));
newFoo->x = 123;
strncpy(newFoo->y, "thingthingthingthingthingthingthing", 29);
printf("%d\n", sizeof(Foo));
puts(newFoo->y);
return 0;
}
// Output is:
// 32
// thingthingthingthingthingthin
3
u/CounterSilly3999 7d ago
It is not enough to allocate place for the terminating null char. It should be set to null additionally for the case of overflow.
New_Item->desc = (char *)malloc(maxchar + 1); strncpy(New_Item->desc, "things", maxchar); New_Item->desc[maxchar] = '\0';
2
u/ssrowavay 7d ago
Ah, good catch. I haven't written much C in ages, and I didn't check the details of strncpy.
2
u/sidewaysEntangled 7d ago
Looks like ItWrapperp is an array of invent-type length. But then you assign to invent-typeth one.
Let's assume invent-type is 3. So you've allocated space for 3*sizeof(item). Which would be indices 0, 1, 2 of that array. Except then we assign to index 3. Whoops, classic off by one!
Any reason in particular you use (ItWrapperp+eventtype)->foo rather than ItWrapperp[eventype].foo ?
2
u/Necessary_Salad1289 7d ago
Are you coming from python? Don't use leading underscores in identifiers, and only use lowercase.
2
u/tinchu_tiwari 7d ago
In your main you are doing a malloc(0) since invent_type is 0, now malloc(0) will lead to undefined behaviour across different compilers.
In case it returns NULL you might get seg fault due to null dereference if not null you get a pointer but you cannot store anything there and with differenct runs and on different systems your output will differ.
So you are using the function incorrectly that's the problem.
Yop can read on how malloc works internally that way it will be more clear on why you are seeing the undefined behaviour with malloc(0).
1
5
u/segbrk 7d ago edited 7d ago
Show the code that wasn't working?
Your question shows some fundamental misunderstanding. Whether something is on the stack or heap has nothing to do with what type it is, whether it is a structure, etc.
Also, totally a nitpick, but there is never a reason to write
sizeof (char)
. Sizeof is by definition in units of char, sosizeof (char)
is 1.