[英]HOW TO: Deep Copy of Linked List
我在 C 中编写的程序有一些问题,我已经超越了我的知识。 总之,我需要将链接列表从一个列表深度复制到另一个列表。 列表中有 malloc 的数据,我需要保留所有数据,而不需要指针指向相同的信息。
我只发布了我认为相关的代码。 如果我遗漏了任何重要的上下文信息,请告诉我。
这是代码:
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);
}
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;
}
}
}
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;
这完全消除了Ap
、 Bp
和c
,并避免了缓冲区溢出问题。 我不确定我是否会像你一样将这两个名字放在一起,但这是你的选择。
显然,这还不够问题……还有其他问题。
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 指针都很好 - 它们可以共享。 假设from
的size
正确为零,那么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
列表。 特别是head
, tail
和size
成员在这里没有归零,并且没有设置 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
时发现错误的情况要容易得多。 我认为后者太容易在正确的海洋中忽略错误的。
同样,您的c
和ret->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.