【问题标题】:How do I avoid a segmentation fault while inserting nodes into a singly linked list?将节点插入单链表时如何避免分段错误?
【发布时间】:2020-04-01 02:06:17
【问题描述】:

当我在控制台上按顺序插入节点时,节点被插入
如何确保我处理所有边界条件?例如,如果用户输入的位置大于列表的大小怎么办?另外,当我尝试在 after 一个节点插入时遇到分段错误,但它在 before 一个节点上工作得很好。这是一张有助于更好地解释我的问题的图片

另外,当我尝试在一个节点之后插入时,我遇到了分段错误,但它在之前一个节点工作得很好。

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

struct Node
{
    int data;
    struct Node *next;
};

struct Node *head = NULL;

struct Node *insert(int x,int pos)
{
    if(head == NULL)
    {
        struct Node *temp = (struct Node*)malloc(sizeof(struct Node));
        temp->data = x;
        temp->next = head;
        head = temp;
        return head;
    }
    else
    {
        int len = 0;
        struct Node *temp = head;
        while(temp!=NULL)
        {
            ++len;
            temp = temp->next;
        }

        if(pos == 1)
        {
            struct Node *temp = (struct Node*)malloc(sizeof(struct Node));
            temp->data = x;
            temp->next = head;
            head = temp;
            return head;
        }
        else
        {
            struct Node *temp = (struct Node*)malloc(sizeof(struct Node));
            temp->data = x;
            temp->next = NULL;
            struct Node *temp1 = head;
            for(int i = 2; i<pos; i++)
            {
                temp1 = temp1->next;
            }
            temp->next = temp1->next;
            temp1->next= temp;
        }

    }

}
void print()
{
    struct Node *temp = head;
    while(temp!=NULL)
    {
        printf("%d ",temp->data);
        temp = temp->next;
    }
    printf("\n");
}
int main()
{
    int n,i,x,pos;
    printf("How many elements?\n");
    scanf("%d",&n);
    for(i = 0; i<n; i++)
    {
        printf("enter the value and the position: \n");
        scanf("%d %d",&x,&pos);
        insert(x,pos);
        print();
    }
    printf("Linked list is: \n");
    print();
}

输出 1

How many elements?
3
enter the value and the position: 
3 2
List is: 3 
enter the value and the position: 
4 3
Segmentation fault (core dumped)

输出 2

How many elements?
3
enter the value and the position: 
3 2
List is: 3 
enter the value and the position: 
4 1
List is: 4 3 
enter the value and the position: 
5 3
List is: 4 3 5 
Linked list is: 
4 3 5 

【问题讨论】:

  • 请勿张贴文字图片。
  • 尝试到处添加 NULL 检查...
  • @n.'pronouns'm。已编辑。
  • @dan1st 我不确定在哪里可以添加更多检查。是不是检查头指针是否为 NULL 或不足以进行插入操作?我在这里的输出似乎是矛盾的。我可以插入第 2、第 1 和第 3 个位置(按此顺序),但不能插入第 2、第 3 和第 1 个位置。请解释一下?
  • 你应该在每个 malloc 之后添加空检查,pos 可能比列表大。

标签: c data-structures struct linked-list singly-linked-list


【解决方案1】:

您正在使插入比需要的更难。您只需要迭代 2 个条件 (1) pos 次或更少,以及 (2) 而下一个指针不是 NULL。您可以通过使用 address ofpointer to 下一个节点进行迭代来大大简化插入操作。您使用地址维护当前节点,并且您的指针始终指向下一个节点。当您迭代pos 次或您的下一个指针是NULL 时,您插入您的节点。见Linus on Understand Pointers

此外,您没有使用来自insert() 的任何返回,因此您的函数原型应该只是void insert (int x, int pos)。虽然您应该避免使用指向列表的全局指针,但对于这个有限示例的目的,这很好。知道您的列表通常应该在它需要的范围内声明,并且指向列表开头的指针(或指向指针的指针)应该作为参数传递,以使列表可用于对其进行操作的任何函数而不是它全球化。

将各个部分放在一起,您的 insert() 函数可简化为:

void insert (int x, int pos)
{
    struct Node **ppn = &head,                  /* pointer to pointer to node */
                 *pn = head,                    /* pointer to node */
                 *node = malloc (sizeof *node); /* allocate for new node */

    if (!node) {                                /* validate allocation */
        perror ("malloc-node");
        exit (EXIT_FAILURE);
    }
    node->data = x;                             /* initialize members values */
    node->next = NULL;

    while (pos-- && pn) {   /* iterate pos times && while pn != NULL */
        ppn = &pn->next;
        pn = pn->next;
    }

    node->next = pn;    /* set next to pointer to node */
    *ppn = node;        /* set node at address to node */
}

将其添加到示例的其余部分,您的完整示例将是:

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

struct Node {
    int data;
    struct Node *next;
};

struct Node *head = NULL;

void insert (int x, int pos)
{
    struct Node **ppn = &head,                  /* pointer to pointer to node */
                 *pn = head,                    /* pointer to node */
                 *node = malloc (sizeof *node); /* allocate for new node */

    if (!node) {                                /* validate allocation */
        perror ("malloc-node");
        exit (EXIT_FAILURE);
    }
    node->data = x;                             /* initialize members values */
    node->next = NULL;

    while (pos-- && pn) {   /* iterate pos times && while pn != NULL */
        ppn = &pn->next;
        pn = pn->next;
    }

    node->next = pn;    /* set next to pointer to node */
    *ppn = node;        /* set node at address to node */
}

/** print all nodes in list */
void print (void)
{
    if (!head) {
        puts ("list-empty");
        return;
    }
    for (struct Node *n = head; n; n = n->next)
        printf (" %d", n->data);
    putchar ('\n');
}

/** delete all nodes in list */
void del_list (void)
{
    struct Node *n = head;
    while (n) {
        struct Node *victim = n;
        n = n->next;
        free (victim);
    }
}

int main()
{
    int n,i,x,pos;

    printf ("How many elements?\n");
    if (scanf ("%d",&n) != 1) {
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }

    for (i = 0; i < n; i++)
    {
        printf ("enter the value and the position: \n");
        if(scanf("%d %d",&x, &pos) == 2) {
            insert (x, pos);
            fputs ("list is: ", stdout);
            print();
        }
    }

    puts ("\nLinked list is:");
    print();
    del_list();    /* free all memory allocated to list */
}

注意:del_list() 函数已添加到与您的列表关联的内存中的 free()。)

使用/输出示例

继续您的 SegFaulting 示例,您现在可以完成插入,例如:

$ ./bin/llatpos
How many elements?
3
enter the value and the position:
3 2
list is:  3
enter the value and the position:
4 3
list is:  3 4
enter the value and the position:
1 0
list is:  1 3 4

Linked list is:
 1 3 4

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出已分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/llatpos
==16615== Memcheck, a memory error detector
==16615== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==16615== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==16615== Command: ./bin/llatpos
==16615==
How many elements?
3
enter the value and the position:
3 2
list is:  3
enter the value and the position:
4 3
list is:  3 4
enter the value and the position:
1 0
list is:  1 3 4

Linked list is:
 1 3 4
==16615==
==16615== HEAP SUMMARY:
==16615==     in use at exit: 0 bytes in 0 blocks
==16615==   total heap usage: 5 allocs, 5 frees, 2,096 bytes allocated
==16615==
==16615== All heap blocks were freed -- no leaks are possible
==16615==
==16615== For counts of detected and suppressed errors, rerun with: -v
==16615== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

  • 这比我希望的要复杂一些,因为我是初学者。但我查看了您的回答,发现它提供了很多信息。谢谢。
  • 是的,我花了一段时间才了解链表中指针的地址如何允许您更改该地址处的节点,而前一个链接保持不变(@987654336 @ 仍然指向同一个地址,那里只是存储了一些新的东西......)。这一切都归结为您是从一开始就正确地学习它,还是以后必须与这个概念作斗争。了解指针的地址如何在其值更新时保持不变是了解指针使用的关键。
【解决方案2】:

C 中的初学者索引(例如数组)从 0 开始。

因此,您还应该使用从 0 开始的列表中位置的值 pf。

逻辑上,如果给定位置大于或等于列表中的节点数,则应将新值附加到列表中。

函数insert 中没有任何地方使用列表的大小。所以这段代码sn -p

    int len = 0;
    struct Node *temp = head;
    while(temp!=NULL)
    {
        ++len;
        temp = temp->next;
    }

没有意义。

如果位置大于列表的大小那么这段代码sn-p

        for(int i = 2; i<pos; i++)
        {
            temp1 = temp1->next;
        }

可以调用未定义的行为,因为在循环内变量temp1 可以设置为NULL

此外,如果位置等于 2,则节点 head 不会更新。

该函数具有非 void 返回类型,但不返回任何内容。

函数insert可以写得更简单,如演示程序所示。

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

struct Node
{
    int data;
    struct Node *next;
};

struct Node *head = NULL;

int insert( int data, size_t pos )
{
    struct Node *temp = malloc( sizeof( struct Node ) );
    int success = temp != NULL;

    if ( success )
    {
        temp->data = data;

        struct Node **current = &head;
        while ( pos-- != 0 && *current != NULL )
        {
            current = &( *current )->next;
        }

        temp->next = *current;
        *current = temp;
    }

    return success;
}

void print()
{
    for ( const struct Node *current = head; current != NULL; current = current->next )
    {
        printf( "%d -> ", current->data );
    }

    puts( "NULL" );
}

int main(void) 
{
/*  
    insert( 3, 2 );
    printf( "Linked list is: " );
    print();

    insert( 4, 3 );
    printf( "Linked list is: " );
    print();
*/

    insert( 3, 3 );
    printf( "Linked list is: " );
    print();

    insert( 1, 0 );
    printf( "Linked list is: " );
    print();

    insert( 2, 1 );
    printf( "Linked list is: " );
    print();

    insert( 4, 3 );
    printf( "Linked list is: " );
    print();

    insert( 5, 10 );
    printf( "Linked list is: " );
    print();

    return 0;
}

程序输出是

Linked list is: 3 -> NULL
Linked list is: 1 -> 3 -> NULL
Linked list is: 1 -> 2 -> 3 -> NULL
Linked list is: 1 -> 2 -> 3 -> 4 -> NULL
Linked list is: 1 -> 2 -> 3 -> 4 -> 5 -> NULL

【讨论】:

  • 非常感谢。解释清楚。解决了问题
猜你喜欢
  • 2015-02-01
  • 2021-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-30
  • 2012-09-22
  • 1970-01-01
相关资源
最近更新 更多