1

I wanted to deep copy a struct to another struct for reasons...

Following the second answer to "Deep copying structs with char arrays in C (How to copy the arrays?") I concocted the following example:

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>

#define THE_ARRAY_SIZE 10

struct the_struct
{
    uint64_t the_member;
};

typedef struct the_struct the_struct_type;

the_struct_type *the_function(the_struct_type *the_parameter)
{
    the_struct_type *the_returned_variable = NULL;
    *the_returned_variable = *the_parameter;
    return the_returned_variable;
}

void the_show( the_struct_type *the_parameter)
{
    printf( "the_member is %" PRIu64 "\n", the_parameter->the_member);
}

int main(int argc, char *argv[]) {
    
    the_struct_type the_source;
    the_struct_type *the_target = NULL;
    
    the_source.the_member = 7777777;
    
    the_target = the_function(&the_source);
    the_show(the_target); 
}

The result was:

Segmentation fault (core dumped)

Out of despair I commented the innards of the function. It was as if an invisible hand was guiding my actions. :)

the_struct_type *the_function(the_struct_type *the_parameter)
{
    //the_struct_type *the_returned_variable = NULL;
    //*the_returned_variable = *the_parameter;
    //return the_returned_variable;
}

And, out of the blue, the result was the desired—not sure if what I expected, though:

the_member is 7777777

I verified this behavior defining more complex structs, containing arrays (but not pointers). This unexpected behavior allowed me to create also arrays of structs by adding the following code to main:

int main (int argc, char *argv[]) {
    
    /* */
    
    the_struct_type *the_array_of_struct_type = calloc(THE_ARRAY_SIZE, sizeof *the_array_of_struct_type);
    the_array_of_struct_type[0] = *the_target;
    the_show( &the_array_of_struct_type[0] );

    the_source.the_member = 123456;
    the_target = the_function( &the_source );
    the_show(the_target);
    
    the_array_of_struct_type[1] = *the_target;
    the_show( &the_array_of_struct_type[1] );    
    the_show( &the_array_of_struct_type[0] );
}

Resulting in:

the_member is 7777777
the_member is 7777777
the_member is 123456
the_member is 123456
the_member is 7777777

From the first answer to "Empty return in non-void function, is undefined behaviour?" I understand this is a constraint violation, but the result is that this non-void C empty function without return statement is deep copying the input struct to the output struct.

I am just going to leave this here, just in case it could be of use for anyone else. But is there more than meets the eye here?

I guess that the compiler is the key here: I am using gcc 7.4.0 under Cygwin x86_64 3.0.7(0.338/5/3) and Windows 10. I compile with a terse:

> gcc code.c -o executable.exe
> gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/7.4.0/lto-wrapper.exe
Target: x86_64-pc-cygwin
Configured with: /cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --disable-libssp --enable-libada --disable-symvers --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible --enable-libstdcxx-filesystem-ts
Thread model: posix
gcc version 7.4.0 (GCC)
chqrlie
  • 131,814
  • 10
  • 121
  • 189
DIVVS IVLIVS
  • 55
  • 2
  • 9
  • 1
    Also, the segmentation fault happened because you're trying to dereference and assign to `NULL`, which is another case of undefined behavior. If you want to copy the contents of a struct, there first has to be a compatible object to copy to, which you didn't provide. – Felix G Jul 17 '20 at 07:38
  • 2
    Using the return value of a non-void function that has fallen off the end (i.e. didn't execute a return statement) is indeed undefined behavior. What likely happend in your case is that `the_parameter` is just interpreted as return value (so nothing was actually copied). Just compare the address of your original struct with the "copied" one and you'll probably find that they're the same – Felix G Jul 17 '20 at 07:54
  • 2
    @DIVVS IVLIVS What has `the_function` to do with the function in Peter's answer? You changed it drastically. – RobertS supports Monica Cellio Jul 17 '20 at 07:55
  • @RobertS_supports_Monica_Cellio Well... *blush* it was just a source of inspiration for my lazy attempt at a struct copying function. – DIVVS IVLIVS Jul 17 '20 at 08:00
  • 1
    @DIVVSIVLIVS the function needs to have something to copy **to**. As it is, you're trying to copy to "nowhere" (or rather `NULL`). If you want that function to copy to a new object, you'll need to use `malloc()` to create that object first (and check the return value to make sure it isn't `NULL`). – Felix G Jul 17 '20 at 08:04
  • 2
    @DIVVSIVLIVS I think I can understand your intent but that is not really "copying". When you omit the `*`s in the second statement in `the_function`, everything is well defined but you just assign the address of the one structure to the destination structure pointer. You actually don't have two structures. You have a pointer which refers to the same structure again. This also would be equal to `the_struct_type * the_function (the_struct_type * the_parameter) { return the_parameter; }` – RobertS supports Monica Cellio Jul 17 '20 at 08:07
  • 1
    `Segmentation fault` is happening because of this `*the_returned_variable = *the_parameter;` line. Just change it to `the_returned_variable = the_parameter`. – Shubham Jul 17 '20 at 08:14
  • 2
    @DIVVSIVLIVS To me it's a bit unclear what you are asking because the title asks one question and the question body contains lots of unrelated stuff. Is your question as the title suggests or are you really asking how deep-copy should be implemented correctly? – Support Ukraine Jul 17 '20 at 08:14
  • 2
    @DIVVSIVLIVS ... and further: You write about deep-copy but there is no deep-copy in the code – Support Ukraine Jul 17 '20 at 08:44
  • 1
    4386427 has made an important point here: many people talk about deep-copy even if they just have a struct with an array or another nested struct. But a true deep-copy is only required if a struct contains pointers, and in that case there's no automatic one-size-fits-all approach. Instead you have to carefully analyze each and every pointer, in order to know how to correctly make a copy (if it is even possible at all). So different structures all need their own distinct deep-copy functions, which can be radically different from each other. – Felix G Jul 17 '20 at 08:59

2 Answers2

4

The result was: Segmentation fault (core dumped)

This is probably because you attempt to dereference the NULL pointer the_return_variable and assign the non-existing pointed object by the first field of the structure, the pointer the_parameter is pointing to, inside of the the_function.

the_struct_type * the_returned_variable = NULL;
*the_returned_variable = *the_parameter;

To dereference a NULL pointer invokes undefined behavior.

Out of despair I commented the innards of the function. ... And, out of the blue, the result was the desired

The result maybe was as desired at your execution but the behavior is undefined or, as Lundin said, it can even be seen as contraint violation to use the return value of a non-void function missing an explicit return statement. Never trust on this behavior.

I understand this is a constraint violation, but the result is that this non-void C empty function without return statement is deep copying the input struct to the output struct.

As said above it is undefined behavior/ a constraint violation. You can't trust on anything. It is also not "copying" the structure's content.

The value of the pointer parameter is probably treated as return value and just assigned to the pointer the_target in main().

When you try to print the value of the presumed copied field in the_show, it in fact only prints the field of the original structure.

Why should a non-void C empty function without return statement copy a struct?

It shouldn't and in fact it doesn't. Peter's function is quite different than the_function in your example.

void deepCopyPerson (struct person *target, struct person *src)
{
    *target = *src;
}
  1. deepCopyPerson is a function returning void.
  2. Here are two parameters (for a pointer to the destination (target) and to the source.
  3. The assignment goes directly from the source to the destination.

Summary: It's completely different.


I wanted to deep copy a struct to another struct for reasons...

Note that if the structure to be copied contains pointer members the assignment as shown in Peter's approach would not be a deep copy. This would be a shallow copy.

Related:

  • 1
    @4386427 I read in the answers to the linked questions about that, too. It seems that "shallow copy" is, when pointers as members are involved and the pointers are just copied one-to-one so that the pointers in B refer to things the pointers in A refer to, which isn't the case here. Everything else, when you only copy data objects or when you copy the structure as shallow copy first and then adjust the pointers in the copied structure seems to be a "deep copy". Not sure whether padding needs to be copied too to be classified as "deep copy" tough. – RobertS supports Monica Cellio Jul 17 '20 at 09:22
  • 2
    I would argue that both terms should only be used for structs containing pointers. That's because both terms have an implied meaning which isn't correct for regular structs. The term "shallow-copy" implies that the copy is incomplete, whereas the term "deep-copy" - at least in my opinion - implies that some kind of special treatment is required for copying. So, i would just use the term "copy" for regular structs, and the other terms only for structs containing pointers. This is of course just my personal opinion on the matter, and doesn't necessarily reflect how those terms are commonly used – Felix G Jul 17 '20 at 09:24
  • 1
    @FelixG Yes, I guess this makes sense, indeed. – RobertS supports Monica Cellio Jul 17 '20 at 09:29
0

If you want to pass the struct or union by the value and copy it to the new struct you do not have to do anything.

typedef struct 
{
    char name[500];
    double val[40];
    int z[400];
}my_s_t;

my_s_t foo(my_s_t s)
{
    my_s_t s1 = s;

    s1.z[4]++;

    return s1;
}

my_s_t foo1(my_s_t *s)
{
    my_s_t s1 = *s;

    s1.z[4]++;

    return s1;
}

void bar()
{
    volatile my_s_t arr[10];

    /* ......*/

    arr[2] = foo(arr[3]);
    arr[4] = foo1(arr + 5);
}

https://godbolt.org/z/8MWqGf

0___________
  • 60,014
  • 4
  • 34
  • 74