动态分配内存时出现“分段错误”

13

以下是发生分段错误的代码片段(没有调用perror):

job = malloc(sizeof(task_t));
if(job == NULL)
    perror("malloc");

更准确地说,gdb表示segfault发生在__int_malloc调用内部,该调用是由malloc进行的子例程调用。
由于malloc函数与其他线程并行调用,最初我认为这可能是问题所在。我正在使用2.19版本的glibc。
数据结构:
typedef struct rv_thread thread_wrapper_t;

typedef struct future
{
  pthread_cond_t wait;
  pthread_mutex_t mutex;
  long completed;
} future_t;

typedef struct task
{
  future_t * f;
  void * data;
  void *
  (*fun)(thread_wrapper_t *, void *);
} task_t;

typedef struct
{
  queue_t * queue;
} pool_worker_t;

typedef struct
{
  task_t * t;
} sfuture_t;

struct rv_thread
{
  pool_worker_t * pool;
};

现在是未来实现:

future_t *
create_future()
{
  future_t * new_f = malloc(sizeof(future_t));
  if(new_f == NULL)
    perror("malloc");
  new_f->completed = 0;
  pthread_mutex_init(&(new_f->mutex), NULL);
  pthread_cond_init(&(new_f->wait), NULL);
  return new_f;
}

int
wait_future(future_t * f)
{
  pthread_mutex_lock(&(f->mutex));
  while (!f->completed)
    {
      pthread_cond_wait(&(f->wait),&(f->mutex));
    }
  pthread_mutex_unlock(&(f->mutex));
  return 0;
}

void
complete(future_t * f)
{
  pthread_mutex_lock(&(f->mutex));
  f->completed = 1;
  pthread_mutex_unlock(&(f->mutex));
  pthread_cond_broadcast(&(f->wait));
}

线程池本身:
pool_worker_t *
create_work_pool(int threads)
{
  pool_worker_t * new_p = malloc(sizeof(pool_worker_t));
  if(new_p == NULL)
    perror("malloc");
  threads = 1;
  new_p->queue = create_queue();
  int i;
  for (i = 0; i < threads; i++){
    thread_wrapper_t * w = malloc(sizeof(thread_wrapper_t));
    if(w == NULL)
      perror("malloc");
    w->pool = new_p;
    pthread_t n;
    pthread_create(&n, NULL, work, w);
  }
  return new_p;
}

task_t *
try_get_new_task(thread_wrapper_t * thr)
{
  task_t * t = NULL;
  try_dequeue(thr->pool->queue, t);
  return t;
}

void
submit_job(pool_worker_t * p, task_t * t)
{
  enqueue(p->queue, t);
}

void *
work(void * data)
{
  thread_wrapper_t * thr = (thread_wrapper_t *) data;
  while (1){
    task_t * t = NULL;
    while ((t = (task_t *) try_get_new_task(thr)) == NULL);
    future_t * f = t->f;
    (*(t->fun))(thr,t->data);
    complete(f);
  }
  pthread_exit(NULL);
}

最后是 task.c 文件:

pool_worker_t *
create_tpool()
{
  return (create_work_pool(8));
}

sfuture_t *
async(pool_worker_t * p, thread_wrapper_t * thr, void *
(*fun)(thread_wrapper_t *, void *), void * data)
{
  task_t * job = NULL;
  job = malloc(sizeof(task_t));
  if(job == NULL)
    perror("malloc");
  job->data = data;
  job->fun = fun;
  job->f = create_future();
  submit_job(p, job);
  sfuture_t * new_t = malloc(sizeof(sfuture_t));
  if(new_t == NULL)
    perror("malloc");
  new_t->t = job;
  return (new_t);
}

void
mywait(thread_wrapper_t * thr, sfuture_t * sf)
{
  if (sf == NULL)
    return;
  if (thr != NULL)
    {
      while (!sf->t->f->completed)
        {
          task_t * t_n = try_get_new_task(thr);
          if (t_n != NULL)
            {
          future_t * f = t_n->f;
          (*(t_n->fun))(thr,t_n->data);
          complete(f);
            }
        }
      return;
    }
  wait_future(sf->t->f);
  return ;
}

队列是lfds无锁队列。
#define enqueue(q,t) {                                 \
    if(!lfds611_queue_enqueue(q->lq, t))             \
      {                                               \
        lfds611_queue_guaranteed_enqueue(q->lq, t);  \
      }                                               \
  }

#define try_dequeue(q,t) {                            \
    lfds611_queue_dequeue(q->lq, &t);               \
  }

当异步调用的数量非常高时,问题就会出现。

Valgrind 输出:

Process terminating with default action of signal 11 (SIGSEGV)
==12022==  Bad permissions for mapped region at address 0x5AF9FF8
==12022==    at 0x4C28737: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

1
有可能其他因素会干扰 malloc 的记账吗? - cnicutar
1
听起来好像是内存在其他地方被破坏了。 - imreal
1
如果需要的话,我可以在这里放上完整的源代码。是的,这可能是你应该做的,因为单独上面的代码片段无法说明段错误的来源。 - barak manos
1
有没有可能在valgrind下运行程序?如果存在内存损坏,valgrind可能能够向您展示何时何地发生了问题。 - Jeremy Friesner
@JeremyFriesner 抱歉,我已经在问题中添加了输出。 - guilhermemtr
显示剩余19条评论
2个回答

21

我找出了问题所在:堆栈溢出。

首先,让我解释一下为什么在malloc内部发生堆栈溢出(这可能是你阅读此内容的原因)。当我的程序运行时,每次开始执行另一个任务(由于我编程的方式),堆栈大小都会增加。但是,对于每个这样的时间,我必须使用malloc分配一个新任务。然而,malloc会进行其他子例程调用,这使得堆栈的大小增加甚至比执行另一个任务的简单调用更多。因此,即使没有malloc,我也会遇到堆栈溢出。但是,因为我使用了malloc,堆栈溢出的时刻是在malloc中,在它通过进行另一个递归调用之前就已经发生了。

下面的图示说明了发生了什么:

初始堆栈状态:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
|        garbage        |
-------------------------
|        garbage        | <- If the stack passes this point, the stack overflows.
-------------------------

在调用malloc时出现了堆栈问题:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
|        malloc         |
-------------------------
|     __int_malloc      | <- If the stack passes this point, the stack overflows.
-------------------------

接着栈再次缩小,我的代码进入了一个新的递归调用:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
| recursive call n      |
-------------------------
|        garbage        | <- If the stack passes this point, the stack overflows.
-------------------------

接着,它在这个新的递归调用中再次调用了 malloc。然而,这一次发生了溢出:

-------------------------
| recursive call n - 3  |
-------------------------
| recursive call n - 2  |
-------------------------
| recursive call n - 1  |
-------------------------
| recursive call n      |
-------------------------
|        malloc         | <- If the stack passes this point, the stack overflows.
-------------------------
|     __int_malloc      | <- This is when the stack overflow occurs.
-------------------------

[接下来的答案更注重于为什么我的代码出现了此问题。]

通常,例如,在递归计算斐波那契数列时,对于特定数字n,堆栈大小会随之线性增长。 然而,在这种情况下,我正在创建任务,使用队列来存储它们,并出队一个(fib)任务以执行。如果你在纸上画出它,你会发现任务数量呈指数级增长,而不是线性增长(还要注意的是,如果我使用堆栈来存储创建的任务,分配的任务数量以及堆栈大小将只随着n线性增长。因此,堆栈随n呈指数级增长,导致堆栈溢出...现在来讲解为什么堆栈溢出会发生在malloc调用内部的原因。所以基本上,正如我上面所解释的,堆栈溢出发生在malloc调用内部,因为这是堆栈最大的地方。所发生的情况是,堆栈几乎要爆炸了,由于malloc在其中调用函数,所以堆栈的增长不仅仅是在调用mywait和fib时。

谢谢大家! 如果不是你们的帮助,我就无法解决这个问题!


1
这正是我猜测的,因为我找不到任何问题。但为了确保这是问题所在,您可以将“top”输出转储到文件中,并检查内存使用量如何增加?对于问题和答案都点赞。 - Jekyll
当我移除了所有的线程后,Valgrind 说这可能是堆栈溢出,尽管这不太可能。我已经将 ulimit 设置得更大,然后可以运行更大的斐波那契数列。当我复制堆栈大小时,只能在前一个数字上加 1。但我会按照你说的去做,以确认。 - guilhermemtr

16

SIGSEGV(段错误)在malloc中发生通常是由于堆破坏引起的。但堆破坏不会导致段错误,因此只有在malloc尝试访问时才会出现。 问题在于创建堆破坏的代码可能在任何地方,甚至远离调用malloc的地方。 通常是malloc内部的next-block指针被您的堆破坏更改为无效地址,因此当您调用malloc时,将取消引用无效指针并出现段错误。

我认为您可以尝试将代码片段与程序的其余部分隔离开来,以减少错误的可见性。

此外,我发现您从未释放过内存,可能存在内存泄漏的情况。

为了检查内存泄漏,您可以运行top命令 top -b -n 1 ,并检查:

RPRVT - resident private address space size
RSHRD - resident shared address space size
RSIZE - resident memory size
VPRVT - private address space size
VSIZE - total memory size

1
问题在于分段错误只会在很多调用之后才发生。 - guilhermemtr
在有人开始贴 "-1" 之前,我将删除代码并发布一个真正的答案 :) - Jekyll
缓冲区溢出是一个麻烦的问题。 - Jekyll
我完全同意。 - guilhermemtr
我已经尝试过这个了,但现在在 lfds611_queue_guaranteed_enqueue 中 malloc 失败了(而且完全相同的错误) :( - guilhermemtr
显示剩余10条评论

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