[英]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.