【问题标题】:Making a deep copy of a struct...making a shallow copy of a struct制作结构的深拷贝...制作结构的浅拷贝
【发布时间】:2011-08-02 11:59:29
【问题描述】:

有类似这个问题的问题,但它们与我的具体问题不够相似,无法继续。

我的问题是关于如何制作以指针为成员的结构的深层副本,以及如何制作以指针为成员的结构的 SHALLOW 副本。然后,仅供参考,如何制作没有指针成员的结构的深层副本以及如何制作没有指针成员的结构的浅层副本(不确定最后一个是否有意义)。

假设我们有这个:

typedef struct Student
{
    char* first_name; 
    char* last_name; 
    int grade;
    long id;
} Student;

这是我创建一个学生的通用函数(标题很难格式化):

Student* create_student(const char* first_name, const char* last_name, int grade,long id)

{

   Student *newStudentp = (malloc(sizeof(Student)));

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

   strncpy(newStudentp -> first_name, first_name, strlen(first_name) + 1);
   strncpy(newStudentp -> last_name, last_name, strlen(last_name) + 1);

   newStudentp -> grade = grade;
   newStudentp -> id = id;


   return newStudentp;
}

我尝试做一个深拷贝和一个浅拷贝;

int main()
{
    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?
    return 0;
}

对于带有指针成员的结构的深拷贝,我知道我们必须创建 OUR OWN 拷贝函数,该函数对指针执行一些明智的操作。什么是明智的……我不确定……所以这是我对这个 DEEP 副本的尝试。

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

}

我的问题的另一部分(没有指针作为成员的结构)可能只能口头解释。

阅读有用的评论后编辑:

浅拷贝: memcpy(s2,s1,sizeof(学生));

深拷贝:

void free_student(Student* stu)
{
    free(stu -> first_name);
    free(stu -> last_name);
}

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

【问题讨论】:

  • free_student 应该在你完成一个学生之后调用,并且不在 copy_Student 中。您首先释放 s2,然后将内容复制到其中。不是个好主意。

标签: c struct


【解决方案1】:

您列出的制作浅拷贝的代码不是;它实际上会破坏堆栈并可能使程序崩溃。

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?

如果您的尺寸正确,那将与s2 = s1; 相同。但是由于您的大小错误,它复制太多并且会覆盖 s2 之后内存中的任何内容。要进行真正的浅拷贝,请不要使用&

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

您用于深拷贝的代码同样是错误的,但您走在正确的轨道上。深拷贝背后的基本思想是你必须复制每个字段;对于非指针类型,这与浅拷贝相同,但对于指针,您必须做一些更聪明的事情。但是,您发布的代码并没有这样做。试试这个吧。

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);
}

请注意,为避免内存泄漏,您还需要在分配新副本之前从 s2 中释放旧名称,创建一个将释放这些名称的 free_Student 函数,并确保 create_Student 复制首先是名称(或者包括“应该免费”标志,这样您就不必复制文字字符串)。

现在,对于没有指针(或其他引用类型)的结构,深拷贝和浅拷贝之间没有区别,因为它本身的数据结构是浅的。

【讨论】:

  • @user411313:所以?它在 POSIX、SVr4 和 4.3BSD 中。如果由于某种原因您处于不提供它的环境中,则使用 strlen、malloc 和 memcpy 编写替换是微不足道的。
【解决方案2】:

浅拷贝和深拷贝的区别可以用一句话解释:浅拷贝拷贝指针;深拷贝复制它们指向的内容。

从你问题的最后一部分开始:如果没有指针,浅拷贝和深拷贝之间没有区别。

您尝试制作浅拷贝在技术上是正确的。不过,这在逻辑上是错误的。您的 delete_student() 函数(frees 和 mallocs 的函数)无法处理浅拷贝。它不知道还有多少其他学生副本,您需要延迟free(),直到删除最后一个副本。

深拷贝有一个非常相关的问题。这在技术上是不正确的。奇怪的是,您的create_student() 函数表明您确实知道如何将char* 复制到另一个具有first_namelast_name 的深层副本。您的 copy_Student 也应该这样做。

【讨论】:

    【解决方案3】:

    轻微免责声明:我假设一个 64 位 gcc 编译器就 sizeof() 以及一个 8 字节对齐。我也意识到这几乎是一个 7 岁的问题,但它在我的谷歌搜索中作为第 1 号弹出,所以我想为可能偶然发现它的其他人澄清一些事情。真的我只是想发表评论,但这样做需要 50 声望。所以这里有另一个答案......

    我不确定原始发布者对指针的理解是什么,但我个人知道我必须在内部停止将它们视为“指向”任何东西,并将它们视为某物的“内存地址”。

    您列出的用于制作浅拷贝的代码有一个微妙的(但可能是灾难性的)疏忽。

    在你的 main() 函数中:

    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?
    

    本地(指针/内存地址)变量 s1 和 s2 被声明(在堆栈上):

    • 学生 *s1(64 位 gcc 上的 8 字节内存地址)
    • 学生 *s2(64 位 gcc 上的 8 字节内存地址)

    s1 和 s2 作为指针是学生结构的内存地址,由于您的 create_Student() 函数正在使用 malloc() 在堆上分配内存(heap 意味着即使在 create_Student() 退出后它也会继续存在)。

    在 s1 或 s2 前面放一个 & 就像是在说:“给我我的学生结构的地址”

    &s1 和 &s2 现在代表 s1 和 s2 指针(或内存地址)的内存位置(在堆栈中)。换句话说,您现在有 2 级指针深度:指向(位于堆栈的)指向(位于堆的)Student 结构的指针。

    通过指定 memcpy(&s2,&s1,sizeof(Student)) 您已要求 memcpy 用堆栈指针 s1 的内容(或地址)覆盖堆栈指针 s2,并损坏 main() 的 24 个字节紧跟从 &s2 开始的 8 个字节和紧跟 &s1 的 24 个字节的堆栈内存。所以引用 Anomie:

    如果你的尺寸正确,那将与 s2 = s1;

    因此,使用“需要复制指针指向的内容”的相同逻辑,您的 copy_Student() DEEP 副本可能如下所示:

    // 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); 
    }
    

    【讨论】:

      【解决方案4】:
      memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2?
      

      在这里,您已经用s1 中的相应指针值覆盖了指针s2s2 中的指针,因此您已经泄漏了内存。

      要执行深层复制,您必须首先释放目标结构指向的所有内存。然后分配足够的内存来保存源结构指向的字符串。现在,strncpy 字符串结束了。

      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); 
      }
      

      【讨论】:

        【解决方案5】:

        与其将其视为副本,不如创建一个新结构,但参数与要复制的结构相同?这是一个微妙的区别,但是您已经有了代码:

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

        wiper 结构具有s2 的克隆。

        要制作浅拷贝,请像使用 s1s2memcpy)那样做,或者干脆:

        s2 = malloc(sizeof(Student));
        *s2 = *s1
        

        【讨论】:

          【解决方案6】:

          而不是这个:

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

          做:

          newStudentp -> last_name = strdup (last_name);
          

          您的深层副本想要做类似的事情(不完全是 cnicutar 建议的):

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

          cnicutar 建议的问题是需要在strcpy之前手动分配缓冲区。

          如果我没记错的话:

          *s2 = *s1;

          会做一个浅拷贝。

          当然,在深拷贝和浅拷贝中,您都必须确保 free 目标指针,否则会出现内存泄漏。但是,如果您深度复制到以前浅复制到的结构,即使 freeing 指针也会导致问题。

          【讨论】:

          • strdup 不是 C 标准,malloc 是
          猜你喜欢
          • 2022-01-15
          • 1970-01-01
          • 2011-08-31
          • 2012-04-12
          • 1970-01-01
          • 2011-01-14
          • 1970-01-01
          • 2015-01-13
          • 2011-09-05
          相关资源
          最近更新 更多