【问题标题】:Segmentation fault occuring with zero-length array零长度数组发生分段错误
【发布时间】:2021-08-09 17:12:42
【问题描述】:

我有这个链表节点结构,它使用零长度数组来存储内存:

typedef struct s_list
{
    size_t          *list_size;
    struct s_list   *prev;
    struct s_list   *next;
    size_t          size;
    char            data[0];
}   t_list;

(list_size是一个包含总列表大小的指针)

我正在使用这个函数来分配一个新节点:

static t_list   *lst_new_element(void *data, size_t size)
{
    t_list  *new_element;

    new_element = malloc(sizeof(t_list) + size);
    if (!new_element)
        return (NULL);
    new_element->size = size;
    memcpy(new_element->data, data, size); // <--- Segfault occurs here
    return (new_element);
}

所以分段错误发生在 memcpy 中,但我不明白为什么,因为我分配了 sizeof(t_list) + size 字节,所以这应该足以对数据执行 memcpy(size)。 此调用发生了段错误:lst_new_element((void *)atoll(argv[1]), sizeof(long long))argv[1]5

感谢您的帮助。

【问题讨论】:

  • 5 (argv[1]) 是否代表在您的环境中可以读取的有效地址? (例如,xv6 中的用户二进制文件将允许这样做)
  • argv[1]"5"(帖子里写的)
  • @PaulOgilvie 我正在使用zero-length arrays
  • 不确定但是...对于灵活的数组成员,我写的是[] - 不是[0]
  • @BitTickler 相反,在过去,人们使用char arr[1]; 之类的东西作为最后一个成员,然后疯狂地将其转换为某个指针或越界访问它。这被称为“结构黑客”,是奇怪错误和损坏代码的常见来源。 Afaik gcc 在 90 年代中期发明了零长度数组作为对抗“结构黑客”的方法。

标签: c memory


【解决方案1】:

您将long long 值传递给您的函数,就好像它是一个有效的void *。然后,您的函数尝试取消引用该指针(无效)以尝试复制它指向的内容。这会触发 undefined behavior 导致崩溃。

您需要将atoll 的返回值分配给一个局部变量,然后将该变量的地址传递给函数。

long long val = atoll(argv[1]);
t_list *l = lst_new_element((&val, sizeof(long long));

此外,使用长度为 0 的数组作为结构的最后一个成员是许多编译器用来实现灵活数组成员的扩展。执行此操作的符合标准的方法是将尺寸留空。

typedef struct s_list
{
    size_t          *list_size;
    struct s_list   *prev;
    struct s_list   *next;
    size_t          size;
    char            data[];
}   t_list;

【讨论】:

  • 哦,是的,我不知道为什么,但我以为它会复制指针的地址,我忘记了我自己的代码是如何工作的 x),谢谢
【解决方案2】:

(void *)atoll 您正在将long long 值转换为指针,这当然是完全错误的。而是将结果存储在一个临时变量中并传递该变量(通过值或引用)。

另外请注意ato... 函数是半过时且危险的,您应该改用strtoll,它具有更好的错误处理能力。

此外(与崩溃无关),自 20 多年以来,零长度数组是 gcc 的一个过时的非标准特性。您应该改用标准的 C 灵活数组成员。它们的工作原理完全相同,只需将代码更改为:char data[];

【讨论】:

    【解决方案3】:

    对于您的函数调用,您需要一个中间变量来存储转换后的值,例如:

    long long llval = atoll(argv[1]);
    lst_new_element(&llval, sizeof(long long));
    

    【讨论】:

      【解决方案4】:

      您可以使用compound literals 分配一个临时数组以将值保存在内存中,而不是像其他答案所建议的那样使用临时变量。

      lst_new_element((long long[]){ atoll(argv[1]) }, sizeof(long long));
      

      【讨论】:

      • 聪明,但是由于编译器会分配一个不可见的变量,所以除了混淆之外什么也得不到。
      • @PaulOgilvie 复合文字只是这里介绍的另一种方法。
      猜你喜欢
      • 2012-05-04
      • 2020-05-25
      • 2012-09-06
      • 2013-05-12
      • 1970-01-01
      • 2022-06-19
      • 2010-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多