cmemorystruct

C Struct Passed By Value Memory Addresses in Called Function


I've seem to run into something I can't really explain. I'm testing out passing a struct (typedef with an alias) by value into a function and printing out the addresses of each. I do realize that once the function returns, at least in this case, the contents of the struct do not change and in fact, the memory location of the struct itself does not change when the function returns.

However, inside of the called function I'm creating a new structure and doing a simple assignment to the passed in by value structure. The weird part is that the addresses for both the passed in and the newly created structs are the same and I can't figure out why. I've tried to research this to no avail and K&R does not really cover this, at least from what I've read thus far.

typedef struct {
    char *name;
    int id;
} PbvStruct;

void modify_strct_ref(PbvStruct s) {

    printf("modify_strct_ref original address: %p\n", &s);

    PbvStruct s2;
    s2.name = "Modified";
    s2.id = 3;

    s = s2;

    printf("modify_strct_ref new struct address: %p\n", &s2);
    printf("modify_strct_ref address: %p\n", &s);
}

void pass_by_value() {

    PbvStruct pbv_s;
    pbv_s.name = "Hello!";
    pbv_s.id = 1;

    printf("BEFORE mod contents,\tname = %s, id = %d, address = %p\n",
            pbv_s.name,
            pbv_s.id,
            &pbv_s);

    modify_strct_ref(pbv_s);
}

The output shows the following:

BEFORE mod contents, name = Hello!, id = 1, address: 0x7ffc619f2900
modify_strct_ref                   original address: 0x7ffc619f28d0
modify_strct_ref                 new struct address: 0x7ffc619f28e0
modify_strct_ref                            address: 0x7ffc619f28d0

I understand that the first two printf will print different addresses since a copy is made to the pass by value function. I also understand that the "new struct, s2" address is different as that's a new memory location. HOWEVER, the part I'm struggling with is why on the last printf, the address is still pointing to the "original address" even though I assigned s = s2;

I do know that there are some weird things that happen when doing these kinds of things with structs but I'm not exactly sure why a direct assignment still shows the original address before assignment to the locally created struct. Any help would be appreciated.

EDIT: I read somewhere that when doing assignments like think the compiler actually copies the fields, could it be that the fields get copied but the actual struct address does not?


Solution

  • I think you're trying to make things too complicated. Take structures out of the picture for a moment. Suppose you have:

    int i = 1;
    int j = 2;
    printf("%p\n", (void *)&i);
    i = j;
    printf("%p\n", (void *)&i);
    

    Do you expect two different addresses to be printed? You shouldn't. Assignment changes the value of the object designated by the left-hand operand, not its address.

    In fact, every object* has a characteristic address that remains the same throughout its lifetime. That's part of the object's identity. This is true regardless of the object's data type, so for structs just as much as for ints. For unions and arrays, too, though arrays are a bit tricky for other reasons.

    In comments, you wrote that you expected different because

    in my dealings with other languages that's the way it works.

    ... but that expresses a misconception. It does not work that way in any language you're likely to have come across, and in the languages you're thinking of, you probably can't even express the same thing.

    C is a bit unusual among high(-ish)-level programming languages in that it gives you access both to individual objects and to their addresses. Contrast with, for example,

    Consider this C code:

    struct example {
        int i, j;
    };
    
    struct example ex1 = { 1, 2 };
    struct example ex2;
    struct example *p1 = &ex1;
    struct example *p2 = &ex2;
    
    printf("variable: %3s; address: %p\n", "ex1", (void *) &ex1);
    printf("variable: %3s; address: %p\n", "ex2", (void *) &ex2);
    printf("variable: %3s; address: %p\n", "p1", (void *) &p1);   // note: &p1, not p1
    printf("variable: %3s; address: %p\n", "p2", (void *) &p2);   // note: &p2, not p2
    printf("variable: %3s; value:   %p\n", "p1", (void *) p1);    // note: p1, not &p1
    printf("variable: %3s; value:   %p\n", "p2", (void *) p2);    // note: p2, not &p2
    
    ex2 = ex1;
    printf("After ex2 = ex1:\n");
    printf("variable: %3s; address: %p\n", "ex2", (void *) &ex2);
    
    p2 = p1;
    printf("After p2 = p1:\n");
    printf("variable: %3s; address: %p\n", "p2", (void *) &p2);
    printf("variable: %3s; value:   %p\n", "p2", (void *) p2);
    

    Output on my machine:

    variable: ex1; address: 0x7ffe40a3dd18
    variable: ex2; address: 0x7ffe40a3dd10
    variable:  p1; address: 0x7ffe40a3dd08
    variable:  p2; address: 0x7ffe40a3dd00
    variable:  p1; value:   0x7ffe40a3dd18
    variable:  p2; value:   0x7ffe40a3dd10
    After ex2 = ex1:
    variable: ex2; address: 0x7ffe40a3dd10
    After p2 = p1:
    variable:  p2; address: 0x7ffe40a3dd00
    variable:  p2; value:   0x7ffe40a3dd18
    

    Observe that ex1, ex2, p1 and p2 all have distinct addresses, which are unchanged by assignment. Observe also that the initial value of p1 is the address of ex1, the initial value of p2 is the address of ex2, and that after the assignment to p2, its new value is the same as the value of p1 (the address of ex1).

    In Java or Python, analogs of p2 = p1 are the only option available for entities of compound types, but that's assignment of references, not assignment of the things to which the references refer. What you are experimenting with in the code presented in the question corresponds to ex2 = ex1, which, again, has no close analogue in Java or Python.

    EDIT: I read somewhere that when doing assignments like think the compiler actually copies the fields, could it be that the fields get copied but the actual struct address does not?

    I think it's better to conceive of it in a uniform way: assignment (where allowed) writes new contents into the storage associated with the destination object. That's the same for ints, doubles, structures, and unions. For structures, that storage comprises storage for all the structure members, so direct assignment to a structure writes into the storage for all the members.

    In ex1 = ex2, the addresses of the strucures determine where the data are read from and where they are written. Of course the addresses themselves don't get copied. They are not stored data (in objects ex1 and ex2); rather, they identify the stored data.


    *Technically, every object whose address is ever computed or observed.

    **I avoid the term "object" here because Java terminology uses it differently than C terminology does. In Java, "object" excludes primitives, but C does not have an analogous distinction between built-in data types and other data types.