繁体   English   中英

HOW TO:链表的深拷贝

[英]HOW TO: Deep Copy of Linked List

我在 C 中编写的程序有一些问题,我已经超越了我的知识。 总之,我需要将链接列表从一个列表深度复制到另一个列表。 列表中有 malloc 的数据,我需要保留所有数据,而不需要指针指向相同的信息。

我只发布了我认为相关的代码。 如果我遗漏了任何重要的上下文信息,请告诉我。

这是代码:

矩阵.h

typedef struct matrix {
        char *name;
        int R;
        int C;
        int dim;
        void (*concat_matrices)( struct matrix *A, struct matrix *B, struct matrix *ret );
} Matrix;

void concat_matrices( Matrix *A, Matrix *B, Matrix *ret ) {
        int L1 = strlen( A->name );
        int L2 = strlen( B->name );
        int len = L1 + L2;
        char *Ap = (char*)malloc(L1*sizeof(char)); strcpy(Ap,A->name);
        char *Bp = (char*)malloc(L2*sizeof(char )); strcpy(Bp,B->name);
        char *c = (char*)malloc(sizeof(char)*(len + 2));
        c[0] = '('; strcat(c, Ap); strcat(c, Bp); c[len+1] = ')';
        ret->name = (char*)malloc(sizeof(char)*(len + 2));
        strcpy(ret->name, c);
        ret->R = A->R; ret->C = B->C;
        ret->dim = ret->R*ret->C;
        free(Ap); free(Bp); free(c);
}

矩阵列表.h

typedef struct node {
        Matrix *M;
        struct node *next;
        struct node *prev;
} Node;

typedef struct matrix_list {
        Node *head;
        Node *tail;
        int size;
        void (*append)( struct matrix_list *list, Matrix *M );
        void (*print)( struct matrix_list *list );
        void (*reverse_print)( struct matrix_list *list );
        void (*delete)( struct matrix_list *list, const char *name );
        void (*delete_tail)( struct matrix_list *list );
        void (*delete_head)( struct matrix_list *list );
        void (*release)( struct matrix_list *list );
        void (*clone_list)( struct matrix_list *from, struct matrix_list *to );
} MatrixList;

...

void clone_list( MatrixList *from, MatrixList *to ) {
        if( from->head == NULL ) {
                to = NULL;
        } else {
                Node *tmp = from->head;
                while( tmp != NULL ) {
                        Matrix *m_copy = (Matrix*)malloc(sizeof(Matrix*));
                        char *c_copy = (char*)malloc(sizeof(char*)*strlen(tmp->M->name));

                        strcpy(c_copy,tmp->M->name);
                        m_copy->name=c_copy;
                        m_copy->R=tmp->M->R;
                        m_copy->C=tmp->M->C;
                        m_copy->concat_matrices = concat_matrices;

                        to->append( to,m_copy );
                        tmp = tmp->next;
                }
        }
}

main.c

chain->print(chain);

MatrixList *chain_copy = (MatrixList*)malloc(sizeof(MatrixList));
set_list_functions( chain_copy );
chain->clone_list(chain, chain_copy);
chain_copy->print(chain_copy);

当我尝试打印克隆时出现问题。 因为我在克隆 function 中进行 malloc'ing,所以我了解数据来自 scope。 我怎样才能做这个副本所以function被称为克隆后有自己版本的数据?


更新:

首先,我要感谢大家花时间回答我的问题,并教我更多关于 C 的知识。 我只编码了大约 3 年。 我还有很多东西要学。 可以在以下位置找到来自 Valgrind 的 0 错误的更新源。

http://matthewh.me/Scripts/c++/matrix_chain/对于其他试图弄清楚像我这样的事情的人。 用户=客人密码=客人。 clone_list function 现在看起来像这样。

void clone_list( MatrixList *from, MatrixList *to ) {
        if( from->head == NULL ) {
                to = NULL;
        } else {
                Node *tmp = from->head;
                while( tmp != NULL ) {
                        Matrix *m_copy = (Matrix*)malloc(sizeof(Matrix));
                        m_copy->name = (char*)malloc(strlen(tmp->M->name) + 1);

                        sprintf( m_copy->name, "%s", tmp->M->name );
                        m_copy->R=tmp->M->R;
                        m_copy->C=tmp->M->C;
                        m_copy->concat_matrices = concat_matrices;

                        to->append( to,m_copy );
                        tmp = tmp->next;
                }
        }
}

如果其他人发现任何其他错误并想添加其他指针,请随时这样做。

您不允许 null 终止字符串,因此您有经典的缓冲区溢出。

您也不需要复制名称三次 您当前的代码是:

int L1 = strlen( A->name );
int L2 = strlen( B->name );
int len = L1 + L2;
char *Ap = (char*)malloc(L1*sizeof(char)); strcpy(Ap,A->name);
char *Bp = (char*)malloc(L2*sizeof(char )); strcpy(Bp,B->name);
char *c = (char*)malloc(sizeof(char)*(len + 2));
c[0] = '('; strcat(c, Ap); strcat(c, Bp); c[len+1] = ')';
ret->name = (char*)malloc(sizeof(char)*(len + 2));
strcpy(ret->name, c);
ret->R = A->R; ret->C = B->C;
ret->dim = ret->R*ret->C;
free(Ap); free(Bp); free(c);

这应该是:

int   L1 = strlen(A->name);
int   L2 = strlen(B->name);

ret->name = (char *)malloc(L1 + L2 + sizeof("()"));  // That adds 3
sprintf(ret->name, "(%s%s)", A->name, B->name);

ret->R   = A->R;
ret->C   = B->C;
ret->dim = ret->R * ret->C;

这完全消除了ApBpc ,并避免了缓冲区溢出问题。 我不确定我是否会像你一样将这两个名字放在一起,但这是你的选择。


显然,这还不够问题……还有其他问题。

void clone_list( MatrixList *from, MatrixList *to ) {
    if (from->head == NULL) {
        to = NULL;
    } else {
        Node *tmp = from->head;
        while( tmp != NULL ) {
            Matrix *m_copy = (Matrix*)malloc(sizeof(Matrix*));
            char *c_copy = (char*)malloc(sizeof(char*)*strlen(tmp->M->name));

            strcpy(c_copy,tmp->M->name);
            m_copy->name=c_copy;
            m_copy->R=tmp->M->R;
            m_copy->C=tmp->M->C;
            m_copy->concat_matrices = concat_matrices;

            to->append( to,m_copy );
            tmp = tmp->next;
        }
    }
}

第一个赋值将本地指针归零; 它对作为目标传入的MatrixList没有任何作用。 这大概应该是:

if (from->head == 0)
{
    *to = *from;
}

这是一个批发结构复制,但将头部和尾部设置为 null,并且 function 指针都很好 - 它们可以共享。 假设fromsize正确为零,那么to也将是正确的。 (同样,这可能不是您正在使用的代码。)

下一个问题是 memory 分配:

Matrix *m_copy = (Matrix*)malloc(sizeof(Matrix*));

这分配了一个指针的价值 memory,而不是一个矩阵的价值。 使用以下任一:

Matrix *m_copy = (Matrix *)malloc(sizeof(*m_copy));
Matrix *m_copy = (Matrix *)malloc(sizeof(Matrix));

这是你麻烦的主要来源(也是valgrind很容易找到的)。


+1被遗忘一次时,它会被遗忘很多次,但这一次不会导致问题,除非名称是空字符串,因为sizeof(char *)乘以 4 或 8(32 位或 64 位) sizeof(char *)而不是预期的sizeof(char)

char *c_copy = (char*)malloc(sizeof(char*)*strlen(tmp->M->name));
strcpy(c_copy,tmp->M->name);

这应该是:

m_copy->name = (char *)malloc(strlen(tmp->M->name) + 1);
strcpy(m_copy->name, tmp->M->name);

我可能会使用old之类的名称而不是tmp 我也疏忽了之前没有提醒您应该认真检查每个 memory 分配的每个回报。 或者为 memory 分配例程使用一组覆盖函数来为您进行检查(通常称为xmalloc()emalloc()等)。

下面的代码似乎没有复制dim成员,如果您依赖它,这是一个错误。

我不完全高兴您似乎依赖于在调用clone_list()之前适当初始化的to列表。 特别是headtailsize成员在这里没有归零,并且没有设置 function 指针。 我会更高兴看到类似的东西:

*to = *from;  // Copy function pointers
to->head = 0;
to->tail = 0;
to->size = 0;
Node *old = from->head;
for (Node *old = from->head; old != NULL; old = old->next)
{
    Matrix *m_copy = clone_matrix(old->M);
    to->append(to, m_copy);
}

甚至:

matrixlist_initialize(to);
Node *old = from->head;
for (Node *old = from->head; old != NULL; old = old->next)
{
    Matrix *m_copy = clone_matrix(old->M);
    to->append(to, m_copy);
}

clone_matrix() function 看起来像:

Matrix *clone_matrix(const Matrix *old)
{
    Matrix *m_copy = (Matrix*)malloc(sizeof(*m_copy));

    m_copy->name = (char*)malloc(strlen(old->name)+1);

    strcpy(m_copy->name, old->name);
    m_copy->R   = old->R;
    m_copy->C   = old->C;
    m_copy->dim = old->dim;
    m_copy->concat_matrices = concat_matrices;
    return(m_copy);
}

我下载了您的代码版本,现在似乎或多或少可以工作。 您应该至少使用-Wall作为编译器选项进行编译; 我拒绝用更少的东西编译,通常也使用-Wextra 我犯了太多容易发现的错误,没有充分利用编译器,当你在学习的时候,你也会。 (我只在 C 中编码超过 25 年;编译器仍然会发现拼写错误和其他愚蠢的错误,但一旦代码编译,我很少遇到大问题。)

当我打开-Wall时,(未使用的) perm() function 出现问题,因为即使它说会返回值,它也没有返回值,并且存在问题,因为main()的正确定义与 arguments是int main(int argc, char **argv)并且您缺少一颗星星。 除此之外,它现在似乎工作正常 - 你可以继续你的开发。

POSIX 中有一个名为strdup()的 function 可以可靠地复制字符串。 它很有用,并且避免了一个接一个的错误。

您应该查看headers的使用。 它们主要用于声明。 如果您明确使用inline (您的代码还没有),则在 header 中包含inline函数可能是合适的,否则,function 主体不应包含在标头中。 它们应该在源文件中( .c后缀)。 每个 header 都应包含使用源提供的功能的代码所需的最少信息。 它不应包含无关的标头,但应包含所有必要的标头。 matrix.h中,您包含了不必要的<stdio.h> 如果您删除了代码,则也不需要<string.h><stdlib.h>

    int L1 = strlen( A->name );
    int L2 = strlen( B->name );
    int len = L1 + L2;
    char *Ap = (char*)malloc(L1*sizeof(char)); strcpy(Ap,A->name);
    char *Bp = (char*)malloc(L2*sizeof(char )); strcpy(Bp,B->name);

我敢打赌,您在此处的strcpy(3)调用已超出您分配的 memory 的范围: strlen(3)不考虑 C 字符串末尾的'\0'终止符,所以您的*Ap*Bp被分配一个字节太短。

因为这常见,所以很容易找到错误:

int len = strlen(s);
char *new = malloc(len); /* FAIL */

如果我在malloc(3)调用中没有看到+ 1 ,那几乎可以肯定是一个错误。 :) 我更喜欢看:

int len = strlen(s);
char *new = malloc(len + 1);

而不是:

int len = strlen(s) + 1;
char *new = malloc(len);

如果您遵循前者,我认为当您忘记+ 1时发现错误的情况要容易得多。 我认为后者太容易在正确的海洋中忽略错误的。

同样,您的cret->name分配的太短了。

但是,我认为有一个更简单的答案:

void concat_matrices( Matrix *A, Matrix *B, Matrix *ret ) {
        int len = strlen(A->name) + strlen(B->name) + 2 + 1; /* parens + NUL */
        ret->name = malloc(len);
        sprintf(ret->name, "(%s%s)", A->name, B->name);
        ret->R = A->R; ret->C = B->C;
        ret->dim = ret->R*ret->C;
}

使用sprintf(3)构建字符串。 您只需要分配一个字符串,这就是您打算保留的字符串,这将减少由于频繁的分配/解除分配周期导致的 memory 碎片。 但重写的最重要原因是我认为这个新版本更容易阅读。 请注意,我在这里打破了关于+ 1的规则——通过将一行所需的所有memory 相加来提高清晰度。

更新

您的clone_list() function 遇到同样的问题:

 Matrix *m_copy = (Matrix*)malloc(sizeof(Matrix*)); char *c_copy = (char*)malloc(sizeof(char*)*strlen(tmp->M->name)); strcpy(c_copy,tmp->M->name);

您的m_copy看起来不错,因为它是二进制 object 并且大小是已知的。 但是c_copy分配的一个字节太短了——它不够长,无法包含在strcpy(3)调用之后立即复制到位的'\0'字节。 这个例程也在乱写未分配的 memory。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM