0

Please consider the following code:

// Thing is a typedef struct
void f(Thing* things, int size) {
    for (int i = 0; i < size; i++) {
        Thing my_thing = things[i];
        my_thing.a = 10;
    }
}

The array pointed to by things is allocated somewhere on the heap.

And yet - my_thing is a "stack variable" - allocated on the stack.

So what is happening behind the scenes? Is things[i] copied to the stack and stored in my_thing? And what does my_thing.a = 10; do - does it modify the original things[i] or does it modify the "stack copy"?

Aviv Cohn
  • 15,543
  • 25
  • 68
  • 131
  • Yes, `things[i]` is copied member by member to the stack, into `my_thing`, and no, `my_thing.a = 10;` does not modify `things[i]`. – Mike Holt Apr 15 '19 at 21:11
  • related: https://stackoverflow.com/questions/2302351/assign-one-struct-to-another-in-c/ – Nellie Danielyan Apr 15 '19 at 21:19
  • You should ask for an array instead of a pointer if you really want an array. `ptrdiff_t` is also more suited as an array size. `void f(ptrdiff_t size, Thing things[size])` – alx - recommends codidact Apr 15 '19 at 21:22
  • @CacahueteFrito, I'm not sure I follow. Ideally, it would be `size_t size`, but `int` is sufficient if the number of elements is within the rage of `int`. Using `ptrdiff_t` doesn't change anything. – David C. Rankin Apr 15 '19 at 21:28
  • @DavidCRankin No, `size_t` is for sizes in bytes. (Read: https://stackoverflow.com/a/3174900). Although usually not needed, it's best to use it so that it self documents the code. And new code should try to be the best possible always :) – alx - recommends codidact Apr 15 '19 at 21:39
  • The same as the pointer vs array thing. Internally they are both pointers, because functions don't accept arrays, but code is more readable if you write what you want. – alx - recommends codidact Apr 15 '19 at 21:41
  • @DavidC.Rankin: I have previously had a chat in comments with Cacahuete Frito about `ptrdiff_t` vs other integer types. I don't seem to have persuaded them — they remain convinced that `ptrdiff_t` is best (whereas I am not; I use `int` or `size_t` most often). – Jonathan Leffler Apr 15 '19 at 21:44
  • @JonathanLeffler `size_t` is ok as an unsigned version of `ptrdiff_t` if you want it. But I would discard `int`. – alx - recommends codidact Apr 15 '19 at 21:48
  • 1
    I know you would discard `int`, and I don't think that's necessary or beneficial, @CacahueteFrito. Like I said, I don't seem to have persuaded you. It's OK; you're allowed to have your own opinions. I may end up having to contradict you a lot, but that's life, sometimes. – Jonathan Leffler Apr 15 '19 at 21:50
  • Incidentally, if you look at the [C11 standard (draft)](http://port70.net/~nsz/c/c11/n1570.html), and search for `ptrdiff_t`, I believe you'll find zero examples of `ptrdiff_t` being used for array indexes, but there are plenty of examples of other types being used as array indexes. – Jonathan Leffler Apr 15 '19 at 21:56
  • You can lead a horse to water... but you can't make 'em drink ... `:)` – David C. Rankin Apr 15 '19 at 22:08
  • Exercise for the OP: What would you do in order to create a logical reference to the same object, instead of creating another object? What's the C construct for that? – Peter - Reinstate Monica Apr 15 '19 at 22:20

4 Answers4

4

Is things[i] copied to the stack and stored in my_thing?

Yes, as a memory copy, not deep copy.

And what does my_thing.a = 10; do - does it modify the original things[i] or does it modify the "stack copy"?

It only modifies stack copy. However be careful when dealing with pointers, see example here

Nellie Danielyan
  • 1,001
  • 7
  • 19
2

The line

    Thing my_thing = things[i];

will copy the whole struct from things[i] to the local my_thing that is on the stack. The amount of data copied will be the size of the struct, so if the struct is large then it can be lots of data copied there.

The line

    my_thing.a = 10;

is only affecting the local my_thing that is on the stack. The original things[i] is never modified. The code will also still work in the same way if you put const on the input pointer, like this:

void f(const Thing * things, int size) {
    for (int i = 0; i < size; i++) {
        Thing my_thing = things[i];
        my_thing.a = 10;
    }
}
Elias
  • 913
  • 6
  • 22
1

my_thing and things[i] are two distinct objects in memory. The line

Thing my_thing = things[i];

creates a new object my_thing and copies the contents of things[i] to it. Any further change to my_thing (such as my_thing.a = 10;) has no effect on things[i], irrespective of whether either is allocated on the stack or heap.

On most implementations, "stack" and "heap" are just different address ranges within the same memory segment - there's no real difference between them except for how the bookkeeping is done for objects within those regions. IOW, it doesn't matter that things[i] is on the heap while my_things is on the stack as far as the mechanics of copying one to the other.

John Bode
  • 119,563
  • 19
  • 122
  • 198
0

First of did you try if this code even works because normally I would write Thing* my_thing = things + i to get each Thing in the array.

You give the function a pointer to a Thing not a Thing Array. That is why things is the address of the first thing.

To get the next you have to add i to the base pointer to get to the wanted address.

How the next line works depends on what type my_thing is.

If it is a pointer like I declared it, it would actually edit the content of the array on the heap and only create a copy of the address in my_thing. However the line should be (*my_thing).a = 10; so that you actually set the value in the address the pointer is pointing on.

If it is Thing my_thing = *(things + i) then a local copy of the value will be created and the next line will change that copy instead of the original array.

I hope this answer can help you understand what is actually happening.