为什么这个基本链表在 MacOS 上工作但在 Linux 上出现段错误

Why does this basic linked list work on MacOS but seg fault on Linux

我有一个偶尔在 MacOS 上使用的链接列表库,我只是尝试在 Linux 上使用它,但我遇到了各种各样的问题。我已将其分解为一个更简单的版本来解决问题。我已经能够找到 gdb 的问题所在,我只是不知道为什么会这样。即使使用地址消毒器,它在 MacO 上也能正常工作。我怀疑我可能以某种方式滥用了这里的指针。

这是我的列表结构:

struct node {
  int value;
  struct node *next;
};
typedef struct node node_t;

struct list {
  node_t *head;
};
typedef struct list list_t;

以及函数:

void list_init(list_t *l)
{
  l = malloc(sizeof(list_t));
  assert(l);
  l->head = NULL;
}

static node_t *new_node(int value)
{
  node_t *new = malloc(sizeof(node_t));
  assert(new);

  new->value = value;
  new->next = NULL;

  return new;
}

void push(list_t *l, int value)
{
  node_t *node = new_node(value);
  if (l->head == NULL) {
    l->head = node;
  } else {
    node->next = l->head;
    l->head = node;
  }
}

void print_list(list_t *l)
{
  node_t *tmp = l->head;
  while (tmp) {
    printf("%d\n", tmp->value);
    tmp = tmp->next;
  }
}

主要功能:

int main()
{
  list_t *l;

  list_init(l);
  push(l, 2);
  push(l, 4);
  push(l, 6);

  print_list(l);

  return 0;
}

gdb 告诉我推送函数 (if (l->head == NULL)) 中的 NULL 检查导致设置错误。但它也告诉我 l->head 确实是 NULL。如果我删除它,段错误只会发生在调用 l->head 的下一个地方。

如果我不将我的列表声明为指针...像这样:

int main()
{
  list_t l;

  list_init(&l);
  push(&l, 2);
  push(&l, 4);
  push(&l, 6);

  print_list(&l);

  return 0;
}

它解决了这个问题。但是,它随后会到达 print_list 函数。它将打印列表,然后打印更多垃圾值,然后出现段错误。

感谢任何帮助。 而且我知道这里没有释放内存。只是试图保持代码小以解决问题。

比如这个函数

void list_init(list_t *l)
{
  l = malloc(sizeof(list_t));
  assert(l);
  l->head = NULL;
}

没有意义,因为函数处理值的副本 list_t *.

类型的参数

因此更改此语句中的副本

  l = malloc(sizeof(list_t));

不影响用作参数的原始指针。所以这个函数实际上调用了未定义的行为。它不初始化列表。

因此在调用此代码片段中的函数后

list_t *l;

list_init(l);

指针 l 保持未初始化状态并且具有不确定的值。另一方面,该函数会产生内存泄漏。

这段代码没有任何变化

list_t l;

list_init(&l);

因为在这个语句

之后的函数内list_init
l = malloc(sizeof(list_t));

该函数处理 list_t 类型的动态分配对象,而不是在 main 中声明的按引用传递的对象 l

要解决这个问题定义函数 list_init like

void list_init(list_t *l)
{
    assert(l);
    l->head = NULL;
}

并这样称呼它

list_t l;

list_init(&l);

push函数可以写得更简单

int push( list_t *l, int value )
{
    node_t *node = new_node( value, l->head );
    int success = node != NULL;

    if ( success )
    {
        l->head = node;
    }

    return success;
}

对应的函数new_node可以这样定义

static node_t * new_node( int value, node_t *next )
{
    node_t *node = malloc( sizeof( node_t ) );

    if ( node != NULL )
    {
        node->value = value;
        node->next = next;
    }
  
    return node;
}

您的 list_init 不可能工作。 C 是按值传递的,所以 list_init 所做的任何事情都不会对 main() 中的指针变量 l 产生任何影响,它仍然充满未初始化的垃圾。你应该得到一个编译器警告(启用 -Wall!!)。

您可能希望 list_init() return 它的指针而不是试图通过引用传递,所以:

list_t *list_init(void)
{
  list_t *l = malloc(sizeof(list_t));
  assert(l);
  l->head = NULL;
  return l;
}
//...
int main()
{
  list_t *l = list_init();
  // ...
}

你的第二个版本同样被破坏了,因为当 list_init() 分配给它的局部变量 l 时,它只是失去了传递给它的指针,所以它所做的任何事情都不会对 lmain 中。您不需要在此版本中分配任何内容,因为您已经传递了一个指向有效 list_t 对象的指针。所以如果你想这样写,那么你只需要

void list_init(list_t *l)
{
  l->head = NULL;
}
// ...
int main()
{
  list_t l;
  list_init(&l);
  // ...
}