簡體   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