Making a deep copy of a struct…making a shallow copy of a struct

后端 未结 6 1861
故里飘歌
故里飘歌 2021-02-01 07:46

There are questions LIKE this one, but they are not similar enough to my specific question FOR ME to pick up on.

My question is about how to make a deep copy of a struct

相关标签:
6条回答
  • 2021-02-01 08:01

    The difference between a shallow copy and a deep copy can be explained in one sentence: A shallow copy copies pointers; a deep copy copies what they point to.

    To start with the last part of your question: if there are no pointers, there's no difference between a shallow or a deep copy.

    Your attempt to make a shallow copy is technically correct. It's logically wrong, though. Your delete_student() function (the one that frees the mallocs) can't deal with shallow copies. It wouldn't know how many other student copies are still around, and ou'd need to delay the free() until the deletion of the alst copy.

    The deep copy has a very related problem. it's technically incorrect. Strangely enough, your create_student function shows that you do know how to copy a char* to another one - that has a deep copy of both first_name and last_name. Your copy_Student should do the same.

    0 讨论(0)
  • 2021-02-01 08:07

    SLIGHT DISCLAIMER: I'm assuming a 64-bit gcc compiler as far as sizeof() as well as an 8-byte alignment. I also realize this is almost a 7 year old question, but it popped up in my google search as number 1 so I wanted to clarify a few things for others that might stumble upon it. Really I just wanted to comment, but SO requires 50 reputation to do so. So here goes another answer...

    I'm not sure what the original poster's understanding of a pointer is, but I know personally I had to internally stop thinking of them as "pointing" to anything and think of them as a "memory address of" something.

    The code you have listed as making a shallow copy has a subtle (but potentially catastrophic) oversight.

    In your main() function:

    Student *s1 = create_Student("Bo","Diddly", 100, 221);
    Student *s2 = create_Student("Leeroy","Jenkins",50,1337);
    memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2?
    

    The local (pointer/memory address) variables s1 and s2 are declared (on the stack):

    • Student *s1 (an 8-byte memory address on 64-bit gcc)
    • Student *s2 (an 8-byte memory address on 64-bit gcc)

    s1 and s2 being pointers are memory addresses of student structs which happen to be allocated in heap memory due to the fact that your create_Student() function is using malloc() which allocates memory on the heap (heap meaning it will stick around even after create_Student() exits).

    Putting an ampersand in front of s1 or s2 is like saying: "Give me the address of the address of my Student struct"

    &s1 and &s2 now represent the memory locations (in the stack) of your s1 and s2 pointers (or memory addresses). In other words you are now 2 levels of pointer deep: a pointer to a (stack located) pointer to a (heap located) Student struct.

    By specifying memcpy(&s2,&s1,sizeof(Student)) you have asked memcpy to overwrite the stack pointer s2 with the contents (or address) of stack pointer s1 as well as corrupt 24 more bytes of main()'s stack memory that immediately follows the 8 bytes starting at &s2 with the 24 bytes that immediately follows &s1. So to quote Anomie:

    If you had the size right, that would be the same as s2 = s1;

    So using the same logic of "needing to make a copy of what pointers point to" your copy_Student() DEEP copy might look like:

    // I swapped the s1 and s2 arguments with
    // target and source for clarity as well as their order
    // to more closely mimic memcpy()
    void copy_Student(Student *target, Student *source)
    {
       if (target!=NULL) free_Student(target); // if target is already allocated, free it...
       assert(source != NULL);
    
       target->grade = source->grade;
       target->id = source->id;
    
       target->last_name = (malloc((strlen(source->last_name) + 1)  * sizeof(char)));
       target->first_name = (malloc((strlen(source->first_name) + 1)  * sizeof(char)));
    
       strncpy(target->first_name, source->first_name, strlen(source->first_name) + 1);
       strncpy(target->last_name, source->last_name, strlen(source->last_name) + 1); 
    }
    
    0 讨论(0)
  • 2021-02-01 08:07
    memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2?
    

    Here you've overwritten the pointer s2 and the pointers within s2 by the corresponding pointer values in s1, so you've leaked memory.

    To perform a deep copy you must first free any memory that was being pointed to by the destination structure. Then allocate enough memory to hold the strings pointed to by the source structure. Now, strncpy the strings over.

    void copy_Student(Student *s1, Student *s2)
    {
       assert( ( s1 != NULL ) && ( s2 != NULL ) );
    
       if( s2->first_name != NULL ) free( s2->first_name );
       if( s2->last_name != NULL ) free( s2->last_name );
    
       s2->grade = s1->grade;
       s2->id = s1->id;
    
       s2->last_name = (malloc((strlen(s1->last_name) + 1)  * sizeof(char)));
       s2->first_name = (malloc((strlen(s1->first_name) + 1)  * sizeof(char)));
    
       strncpy(s2-> first_name, s1->first_name, strlen(s1->first_name) + 1);
       strncpy(s2-> last_name, s1->last_name, strlen(s1->last_name) + 1); 
    }
    
    0 讨论(0)
  • 2021-02-01 08:18

    Instead of this:

    newStudentp -> last_name = (malloc((strlen(last_name) + 1)  * sizeof(char)));
    

    do:

    newStudentp -> last_name = strdup (last_name);
    

    Your deep copy wants to do something similar (not exactly what cnicutar suggested):

    s2->first_name = strdup (s1->first_name);
    

    The problem with cnicutar's suggestion is that it needs to manually allocate the buffer before the strcpy.

    And if I remember correctly:

    *s2 = *s1;

    will do a shallow copy.

    Of course, in both the deep and shallow copies you must make sure that you free the destination pointers, otherwise you'll get a memory leak. But even freeing the pointers can lead to problems if you deep copy to a structure that was previously shallow copied to.

    0 讨论(0)
  • 2021-02-01 08:19

    The code you have listed as making a shallow copy isn't; it will actually smash the stack and probably crash the program.

    Student *s1 = create_Student("Bo","Diddly", 100, 221);
    Student *s2 = create_Student("Leeroy","Jenkins",50,1337);
    memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2?
    

    If you had the size right, that would be the same as s2 = s1;. But since you have the size wrong, it is copying too much and will overwrite whatever is in memory after s2. To do a real shallow copy, leave off the &:

    memcpy(s2,s1,sizeof(Student)); //shallow copy of s1 INTO s2
    

    The code you have for a deep copy is similarly wrong, but you're on the right track. The basic idea behind a deep copy is that you have to copy each field; for non-pointer types this is the same as a shallow copy, but for pointers you have to do something smarter. The code you posted, however, isn't doing that. Try this instead.

    void copy_Student(Student *s1, Student *s2)
    {
        s2 -> grade = s1 -> grade;
        s2 -> id = s2 -> id;
        s2 -> first_name = strdup(s1 -> first_name);
        s2 -> last_name = strdup(s1 -> last_name);
    }
    

    Note that to avoid memory leaks, you would also need to free the old names from s2 before assigning the new copies, make a free_Student function that would free these names, and also make sure that create_Student copies the names in the first place (or else include "should free" flags so you don't have to copy literal strings).

    Now, for a struct without pointers (or other reference types), there is no difference between a deep and a shallow copy because the data structure it itself shallow.

    0 讨论(0)
  • 2021-02-01 08:19

    Instead of thinking about it as a copy, why don't you create a new struct but with the same parameters as the one you want to duplicate? It is a subtle difference but, you have the code already:

    Student *s2 = create_Student("Leeroy","Jenkins",50,1337);
    Student *wiper = create_Student(s2->first_name, s2->last_name, 
                                                   s2->grade, s2->id);
    

    the wiper struct has a clone of s2.

    To make a shallow copy, do as you are doing with s1 and s2 (the memcpy), or simply:

    s2 = malloc(sizeof(Student));
    *s2 = *s1
    
    0 讨论(0)
提交回复
热议问题