为什么我需要使用 strdup()?

why do I need to use strdup()?

typedef struct Node {
    char *word;
    struct Node *next;
} Node;

Node* newNode(char *word) {

    Node *n = malloc(sizeof(Node));
    n->word = word;
    n->next = NULL;
    return n;
}

在此代码(单链表)中,如果我创建许多节点,它们都具有最后一个节点的名称,我需要理解为什么在 newNode 函数中我需要使用 strdup() 函数,当我搜索解决方案时,在这行代码中 n->word = strdup(word); 并在堆中创建一个单词副本。

如果我使用 malloc(sizeof(Node));,这意味着在堆中为这个节点保留一个位置,这样每个节点都应该是独立的,为什么它们共享最后一个节点的名称?

因为 word 是一个指向字符串的指针,所以当 malloc(sizeof(Node)) 时,您只为指针分配 space,而不是为字符串本身分配

这就是为什么你必须单独初始化 n->word(注意 strdup() 为你做了两件事:它分配内存并将字符串复制到其中,然后 returns 指针)。

这条线和你想的不一样:

n->word = word

您需要使用 strdup()(顺便说一下,这不是 C18 的标准函数,但可能在 C2x 中)单独为字符串分配内存。上面一行只是复制了字符串的地址,所以 n->wordword 指向同一个字符串。此行创建一个具有相同内容的新字符串:

n->word = strdup(word);

或者,要符合标准:

n->word = malloc((strlen(word) + 1) * sizeof(char));
strcpy(n->word, word);

表示你传递给函数newNode

Node* newNode(char *word) {

    Node *n = malloc(sizeof(Node));
    n->word = word;
    n->next = NULL;
    return n;
}

指向同一个字符数组的第一个字符的指针,该数组的内容在调用函数的代码中发生了变化,但数组的地址没有改变,即您正在使用同一个数组。

您需要复制传递给函数的指针指向的字符串。在这种情况下,函数看起来会更复杂

Node* newNode( const char *word ) 
{
    Node *n = malloc( sizeof( Node ) );
    int success = n != NULL;

    if ( success )
    {
        n->word = malloc( strlen( word ) + 1 );
        success = n->word != NULL;

        if ( success )
        {
            strcpy( n->word, word ); 
            n->next = NULL;
        }
        else
        {
            free( n );
            n = NULL;
        }
    }

    return n;
}

函数的调用者应检查获取的指针是否等于NULL或不等于NULL

这是一个简单的演示程序,展示了如何使用该函数将新节点添加到列表中。注意函数 strdup 不是标准的 C 函数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node {
    char *word;
    struct Node *next;
} Node;

Node* newNode( const char *word ) 
{
    Node *n = malloc( sizeof( Node ) );
    int success = n != NULL;

    if ( success )
    {
        n->word = malloc( strlen( word ) + 1 );
        success = n->word != NULL;

        if ( success )
        {
            strcpy( n->word, word ); 
            n->next = NULL;
        }
        else
        {
            free( n );
            n = NULL;
        }
    }

    return n;
}

int append( Node **head, const char *word )
{
    Node *new_node = newNode( word );
    int success = new_node != NULL;

    if ( success )
    {
        while ( *head != NULL ) head = &( *head )->next;

        *head = new_node;
    }

    return success;
}

void display( Node *head )
{
    for ( ; head != NULL; head = head->next )
    {
        printf( "\"%s\" -> ", head->word );
    }

    puts( "null" );
}

int main(void) 
{
    Node *head = NULL;
    const char *word = "Hello";

    append( &head, word );

    word = "World";

    append( &head, word );

    display( head );

    return 0;
}

程序输出为

"Hello" -> "World" -> null

您的节点仅包含一个指针,该指针需要指向内存中存储您的实际单词的某个位置。

也许这个例子会帮助你理解。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node {
    char *word;
    struct Node *next;
} Node;

Node* newNode(char *word) 
{
    Node *n = malloc(sizeof(Node));
    n->word = word;
    n->next = NULL;
    return n;
}

Node* insertNode(Node* head, Node* n) 
{
    n->next = head;
    return n;
}

void printList(Node* head)
{
    while(head)
    {
        printf("%s\n", head->word);
        head = head->next;
    }
}

int main(void) {

    // Step 1: Create a list that "works" due to use of string literals
    Node* head1 = NULL;
    head1 = insertNode(head1, newNode("world"));
    head1 = insertNode(head1, newNode("hello"));
    head1 = insertNode(head1, newNode("test"));
    printList(head1);

    printf("------------------------------------------------\n");

    // Step 2: Create a list that "fails" due to use of a char array
    Node* head2 = NULL;
    char str[20];
    strcpy(str, "test");
    head2 = insertNode(head2, newNode(str));
    strcpy(str, "hello");
    head2 = insertNode(head2, newNode(str));
    strcpy(str, "world");
    head2 = insertNode(head2, newNode(str));
    printList(head2);

    printf("------------------------------------------------\n");

    // Step 3: Change the value that head2 nodes points to
    strcpy(str, "What!!");
    printList(head2);

    return 0;
}

输出:

test
hello
world
------------------------------------------------
world
world
world
------------------------------------------------
What!!
What!!
What!!

第 1 步:

head1 列表按预期工作,因为每个节点都使用指向存储在内存中某处的字符串文字的指针进行初始化。每个字符串文字都存储在不同的内存中。因此它工作正常。

第 2 步:

head2 列表没有像您预期的那样工作。这是因为每个节点都是用 str 初始化的,所以所有节点都简单地指向 str 数组。因此,所有节点都指向 "world",即最后一个单词复制到 str.

第 3 步:

然后一个新词,即 "What!!" 被复制到 str 数组中,现在每个节点将再次打印 str 的内容,即 "What!!"。

结论

这完全取决于你如何称呼newNode

如果你每次都用指向某个新内存的指针调用它,你不需要将单词复制到新位置(或使用strdup)。

但是如果您在调用 newNode 时重复使用缓冲区,您将需要复制到 newNode 中的其他内存(而 strdup 是进行该复制的一种方法)

基本上,在 C++ 中没有类型 "String"。字符串是一堆在数组中对齐的字符。这意味着字符串是一个指针。所以strdup让你复制字符串的内容而不复制那个字符串的地址。