【问题标题】:Fixing Segmentation Fault Sometimes in C修复有时在 C 中的分段错误
【发布时间】:2017-01-07 02:08:00
【问题描述】:

我是学习c的初学者,分割对我来说发生了很多次。我也在网上做了一些关于分段错误的研究:一些原因是分配内存问题、空指针或内存访问问题。但是我很困惑,为什么有时代码可以工作,但有时却说分段错误?以下是我在 insertAtdestroyList 函数中遇到此问题的代码:

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

typedef struct NODE{
    int data;
    struct NODE* next;
} node;

node* insertAt(node*, int, int);
void printList(node*);
void destroyList(node*);

node* myList;
int counter = -1;

int main()
{
    myList = NULL;
    int pos, input;

    myList = insertAt(myList, 0, 333);
    myList = insertAt(myList, 0, 555);
    myList = insertAt(myList, 1, 222);
    myList = insertAt(myList, 3, 444);

    printf("My List:\n");
    printList(myList);

    destroyList(myList);

    printf("After Destroy:\n");
    printList(myList);

    return 0;
}

node* insertAt(node* head, int pos, int newData)
{
    node* temp = (node*) malloc(sizeof(node));
    temp->data = newData;
    counter++;

    if(head == NULL){
        head = temp;
        return head;

    }else if (pos == 0)
    {
        temp->next = head;
        head = temp;
        return head;

    }else if(head != NULL && pos > counter){
        node* current = head;
        node* temp2 = current;
        while(current != NULL){
            temp2 = current;
            current = current->next;
        }
        temp->next = current;
        temp2->next = temp;
        return head;

    }else
    {
        node* current = head;
        while(pos-1>0){
            current = current->next;
            pos--;
        }
        temp->next = current->next;
        current->next = temp;
        return head;
    }
}

void printList(node* head)
{
    node* ptr = head;

    while (ptr != NULL)  {
        printf("%i ", ptr->data);
        ptr = ptr->next;
    }
    printf("\n");
}

void destroyList()
{
    node* temp;
    while(myList){
        temp = myList;
        myList = temp->next;
        free(temp);
    }
}

【问题讨论】:

  • 你有一个非常奇怪的变量划分为全局变量和局部变量......
  • 尝试使用gdb调试
  • 全局变量 counter 不会为 next 列表插入重置。使其成为insertAt 中的局部变量,并在函数开始时对其进行初始化。除非完全必要,否则请不要使用全局变量。
  • 在我将node* head 添加到您的destroyList 定义中,并用head 替换其主体中每次使用myList 之前,您的程序不会为我编译。避免使用全局变量。
  • @SuvP 在 c11 模式下使用 clang 3.8 编译,我的工具链也没有发出警告/错误。我希望看到你做了什么,但它不仅编译了,它实际上运行(当然是直接进入 UB)。在源代码底部定义的参数列表中填充void,最终让编译器呕吐。

标签: c linked-list segmentation-fault


【解决方案1】:

在 gdb 下运行你的程序,如下所示:

$ gdb ./a.out
(gdb) run
...
Segmentation fault
(gdb) bt

这将打印回溯,显示代码中导致错误的位置以及调用它的任何函数。如果段错误发生在库函数中,请继续查看回溯,直到找到您的代码,然后看看可以在那里修复什么。

【讨论】:

  • 这是个好建议,但并不是问题的真正答案。
  • 是的,这是一个答案。您可以使用它来找出程序出现段错误的原因。
  • 这不是对所提出问题的回答,因为我将其解释为“为什么这个程序有时会出现段错误?”。您的建议会做出比回答更好的评论。
  • 您是否建议我自己在 gdb 中运行他的程序,然后将结果作为答案发布?抱歉,这太荒谬了。
  • 不,我建议您发布的答案应该是评论。你是否选择为这个问题写一个合适的答案是完全不同的事情。但是,是的,有些人有时会编译和运行 SO 问题中提供的代码以开发答案。这就是我们热衷于提供 MCVE 的原因之一。这并不荒谬,就是这样。
【解决方案2】:

我也在网上做了一些关于分段错误的研究:一些原因是分配内存问题、空指针或内存访问问题。

分段错误几乎总是表明您的程序试图访问不属于它的内存,或者以某种方式不允许访问它。您可以将其分解为程序可能执行此操作的不同方式,但一般规则是确保您仅取消引用有效指针,并且您尝试仅修改可修改的数据。

但我很困惑,为什么有时代码可以工作,但有时却显示分段错误?

C 没有指定任何特定行为都会产生分段错误。该标准甚至不包含术语“分段错误”。然而,它确实谈到了“未定义的行为”——如果你执行的代码不遵守 C 及其标准库的语义规则,就会出现这种情况。

分段错误是许多系统上未定义行为的一种可能表现形式,但这超出了 C 的范围。 C 不承诺在任何特定情况下任何特定形式的未定义行为 - 它不能,因为该行为将被定义,而不是未定义。因此,可以看到的其他形式的未定义行为之一是程序员想要的任何行为。实际上有时会看到这种情况。

此外,给定程序可能具有未定义的行为——这可能表现为分段错误——仅在某些条件下,例如特定输入。

在任何情况下,您的程序有时甚至总是(就您可以确定的而言)按预期运行并不能证明它没有未定义的行为。


对于您的特定代码,其中有几个缺陷可能导致它有时表现出未定义的行为。其中有:

  • 您使用malloc() 的返回值而不检查它是否为NULL。 malloc() 通过返回 NULL 表示内存分配失败,如果您稍后尝试取消引用,则会调用未定义的行为。

  • 当它将初始节点插入列表并在列表末尾添加节点时,insertAt() 无法设置新节点的next 指针,从而使该指针具有不确定的值。当任何函数稍后遍历列表时,它会评估那个不确定的值,这会产生未定义的行为。在实践中,如果不确定的值变成空指针值,这可能会发生预期的行为。这绝不能保证,但也并非完全不可能。

  • 您的main() 函数尝试通过将当时无效的myList 指针传递给printList() 来打印在destroyList() 中释放后的列表。

【讨论】:

    【解决方案3】:

    您想要的工具是Valgrind。它会为你发现各种隐藏的内存问题。

    例如,此代码“有效”。

    $ cat test.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        char *string = calloc(3, sizeof(char));
    
        strcpy(string, "foo");
        printf("%s\n", string);
    
        free(string);
    
        return 0;
    }
    

    但 Valgrind 发现了细微的非一内存错误。

    $ make
    cc -Wall -g    test.c   -o test
    $ ./test
    foo
    $ valgrind ./test
    ==62034== Memcheck, a memory error detector
    ==62034== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==62034== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==62034== Command: ./test
    ==62034== 
    ==62034== Invalid write of size 1
    ==62034==    at 0x10043B5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
    ==62034==    by 0x1001B8421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x10022BBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x100000F2C: main (test.c:9)
    ==62034==  Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
    ==62034==    at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
    ==62034==    by 0x100000F11: main (test.c:7)
    ==62034== 
    ==62034== Invalid read of size 1
    ==62034==    at 0x10000B2C8: strlen (vg_replace_strmem.c:470)
    ==62034==    by 0x1001EDA4B: __vfprintf (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x1002166C0: __v2printf (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x1001EC381: vfprintf_l (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x1001EA21B: printf (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x100000F42: main (test.c:10)
    ==62034==  Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
    ==62034==    at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
    ==62034==    by 0x100000F11: main (test.c:7)
    ==62034== 
    foo
    ==62034== 
    ==62034== HEAP SUMMARY:
    ==62034==     in use at exit: 26,553 bytes in 188 blocks
    ==62034==   total heap usage: 273 allocs, 85 frees, 32,788 bytes allocated
    ==62034== 
    ==62034== LEAK SUMMARY:
    ==62034==    definitely lost: 0 bytes in 0 blocks
    ==62034==    indirectly lost: 0 bytes in 0 blocks
    ==62034==      possibly lost: 0 bytes in 0 blocks
    ==62034==    still reachable: 0 bytes in 0 blocks
    ==62034==         suppressed: 26,553 bytes in 188 blocks
    ==62034== 
    ==62034== For counts of detected and suppressed errors, rerun with: -v
    ==62034== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 19 from 19)
    

    要摄取的内容很多,但重要的几行位于 test.c 及其上方。正在查看第一条消息...

    ==62034== Invalid write of size 1
    ==62034==    at 0x10043B5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
    ==62034==    by 0x1001B8421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x10022BBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
    ==62034==    by 0x100000F2C: main (test.c:9)
    ==62034==  Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
    ==62034==    at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
    ==62034==    by 0x100000F11: main (test.c:7)
    

    这是一条错误消息,后面是导致它的函数调用堆栈,然后是可能导致它的任何相关错误。

    大小为 1 的无效写入”告诉我我已经从分配的内存中走了一个字节。 by 0x100000F2C: main (test.c:9) 说它发生在 test.c 的第 9 行,即 strcpy,调用链中的上述行证实了这一点(它说 stpcpy,因为 strcpy 可能只是 stpcpy 周围的一个宏)。

    地址 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd”告诉我错误可能是错误的内存分配的结果。 by 0x100000F11: main (test.c:7) 表示它在 test.c 第 7 行,即 calloc 调用,由堆栈中的下一个调用 at 0x10000A1B9: calloc (vg_replace_malloc.c:715) 确认。

    这一切都表明我分配的字节比我需要的少。一个 3 字节的字符串需要 4 个字节,因为 C 字符串有一个尾随空字符。

    (P.S. 不要使用strcpy。)

    但我很困惑,为什么有时代码可以工作,但有时却显示分段错误?

    这是因为 C 允许您随意涂鸦任何您想要的内存。现代操作系统至少将您保留在自己进程的内存中。如果您覆盖重要的东西,或者如果您尝试读取或写入您不应该的内存,这可能会导致各种问题......或者您会很幸运!由于每次分配的内存略有不同,因此每次运行有内存问题的程序可能会有不同的表现。

    我上面的示例代码即使写入无效内存也能正常工作。它只是一个字节,所以也许它很幸运并写了一个没人关心的地方。或者calloc 分配的内存比它需要的多一点。

    重点是,学习 valgrind 或其他内存检查器,运行它,并修复它所说的任何被破坏的东西。

    (P.S. 为valgrind 安装您的操作系统包。它将针对您的操作系统的错误和怪癖进行调整。否则您可能会收到有关操作系统自身代码的各种警告。)

    【讨论】:

    • Valgrind 是一个合适的工具,是的。但是请注意,问题中提供了函数实现——您只需在 iframe 中向下滚动即可。
    • @JohnBollinger D'oh!
    猜你喜欢
    • 2019-03-31
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    • 2021-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多