malloc()如何导致SIGSEGV错误?

10

我在我的程序中遇到了一个奇怪的bug,看起来似乎是malloc()引起了SIGSEGV错误,就我所知道的,这是毫无意义的。我正在使用一个叫做simclist的动态列表库。

这里有一个稍后会被引用的结构体:

typedef struct {
    int msgid;
    int status;
    void* udata;
    list_t queue;
} msg_t;

这里是代码:

msg_t* msg = (msg_t*) malloc( sizeof( msg_t ) );

msg->msgid = msgid;
msg->status = MSG_STAT_NEW;
msg->udata = udata;
list_init( &msg->queue );

list_init 是程序失败的地方,下面是 list_init 的代码:

/* list initialization */
int list_init(list_t *restrict l) {
    if (l == NULL) return -1;

    srandom((unsigned long)time(NULL));

    l->numels = 0;

    /* head/tail sentinels and mid pointer */
    l->head_sentinel = (struct list_entry_s *)malloc(sizeof(struct list_entry_s));
    l->tail_sentinel = (struct list_entry_s *)malloc(sizeof(struct list_entry_s));
    l->head_sentinel->next = l->tail_sentinel;
    l->tail_sentinel->prev = l->head_sentinel;
    l->head_sentinel->prev = l->tail_sentinel->next = l->mid = NULL;
    l->head_sentinel->data = l->tail_sentinel->data = NULL;

    /* iteration attributes */
    l->iter_active = 0;
    l->iter_pos = 0;
    l->iter_curentry = NULL;

    /* free-list attributes */
    l->spareels = (struct list_entry_s **)malloc(SIMCLIST_MAX_SPARE_ELEMS * sizeof(struct list_entry_s *));
    l->spareelsnum = 0;

#ifdef SIMCLIST_WITH_THREADS
    l->threadcount = 0;
#endif

    list_attributes_setdefaults(l);

    assert(list_repOk(l));
    assert(list_attrOk(l));

    return 0;
}

这行代码 l->spareels = (struct list_entry_s **)malloc(SIMCLIST_MAX_SPARE_ELEMS * 是导致 SIGSEGV 的原因。我正在使用 gdb/nemiver 进行调试,但是一直没有头绪。第一次调用此函数时它可以正常工作,但第二次调用时总是失败。malloc() 如何导致 SIGSEGV?
以下是堆栈跟踪信息:
#0  ?? () at :0
#1  malloc () at :0
#2  list_init (l=0x104f290) at src/simclist.c:205
#3  msg_new (msg_switch=0x1050dc0, msgid=8, udata=0x0) at src/msg_switch.c:218
#4  exread (sockfd=8, conn_info=0x104e0e0) at src/zimr-proxy/main.c:504
#5  zfd_select (tv_sec=0) at src/zfildes.c:124
#6  main (argc=3, argv=0x7fffcabe44f8) at src/zimr-proxy/main.c:210

任何帮助或见解都非常感激!

顺便提一下,不建议多次调用srandom()。为了避免未来的错误,即使已知list_init()只会被调用一次,您也应该将种子放在更明显执行一次的地方,比如在main()的顶部附近。 - RBerteig
6个回答

29

malloc 可能会导致段错误,例如当堆被破坏时。请检查您是否在任何先前分配的空间范围之外写入了任何内容。


24
使用Valgrind! 其他帖子中提到了这个工具,但可悲的是没有得到太多赞同。这个回答没有问题,但任何有关内存损坏的讨论都不完整,如果没有提醒使用Valgrind! - Andy Ross
2
当我调用free()时,出现了sigsegv错误。我在valgrind下运行代码,发现我已经超出了缓冲区并破坏了堆栈。 - J. Andrew Laughlin

17

你的代码中可能存在其他部分导致内存违规。如果您使用Linux系统,一定要尝试使用valgrind来检测。除非通过了valgrind的检测,否则我不会相信自己的C程序。

编辑:另一个有用的工具是Electric fence。Glibc还提供了MALLOC_CHECK_环境变量以帮助调试内存问题。这两种方法对运行速度的影响不及valgrind大。


12

在调用此函数之前,您可能已经通过缓冲区溢出或使用未由 malloc 分配(或已被释放)的指针调用了 free 函数,从而破坏了堆。

如果 malloc 使用这种方式受损的内部数据结构,则使用无效数据,可能会导致崩溃。


4

有很多方式可以从malloc()(以及realloc()calloc())触发核心转储。其中包括:

  • 缓冲区溢出:在分配的空间之外写入(践踏了malloc()保留在那里的控制信息)。
  • 缓冲区下溢:在分配的空间开始之前写入(践踏了malloc()保留在那里的控制信息)。
  • 释放未被malloc()分配的内存。在混合使用C和C++程序中,这将包括释放由new在C++中分配的内存。
  • 释放指向由malloc()分配的内存块的一部分位置的指针-这是前面情况的一个特例。
  • 释放已经被释放的指针-臭名昭著的“双重释放”。

使用诊断版本的malloc()或启用系统标准版本的诊断功能,可以帮助识别其中一些问题。例如,它可以检测小的下溢和上溢(因为它分配额外的空间来提供您请求的空间周围的缓冲区),并且它可能可以检测试图释放未分配或已经释放或指向分配空间的一部分位置的内存-因为它将信息与分配的空间分开存储。代价是调试版本需要更多的空间。一个真正好的分配器将能够记录堆栈跟踪和行号,以告诉您在代码中发生了哪个分配,或者第一个释放发生在哪里。


2
您应该尝试在隔离环境中调试此代码,以查看问题是否实际上位于生成段错误的位置。(我怀疑不是)。
这意味着:
1. 使用-O0编译代码,确保gdb获取正确的行号信息。
2. 编写一个单元测试来调用代码的这一部分。
我猜当单独使用该代码时,它将正常工作。然后您可以以同样的方式测试其他模块,直到找出导致错误的原因。
正如其他人建议的那样,使用Valgrind也是一个非常好的想法。

0
代码存在问题。如果malloc返回NULL,则您的代码没有正确处理此情况。您只是假设内存已经为您分配,而实际上并没有分配。这可能会导致内存损坏。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接