零长度数组导致段错误发生

3

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

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)。

感谢帮助。


argv[1]"5"(它在帖子中被写出)。 - Fayeure
@PaulOgilvie 我正在使用零长度数组 - Fayeure
1
不确定,但是对于可变数组成员,我写[]而不是[0] - Support Ukraine
@Fayeure gcc并不是第一个支持零长度数组的C编译器。即使在早期,将这样的数组作为结构体中的最后一个成员是一种常见的习惯用法。我想这和realloc()一样古老。对于许多用例来说,它们往往是成对出现的。 - BitTickler
1
@BitTickler 相反,在过去,人们使用类似 char arr[1]; 这样的东西作为最后一个成员,然后疯狂地将其转换为某个指针或越界访问它。这被称为“结构体黑客”,是奇怪的错误和破损代码的常见来源。据我所知,gcc 在 90 年代中期发明了零长度数组来治愈“结构体黑客”。 - Lundin
显示剩余10条评论
4个回答

3
你正将一个 long long 值作为有效的 void * 传递给你的函数。然后你的函数试图去解引用这个指针(它是无效的),并尝试复制它所指向的内容。这将触发 未定义行为,导致程序崩溃。
你需要将 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),谢谢。 - Fayeure

2

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

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

2

(void *)atoll函数将一个long long值转换成一个指针,这是错误的。相反,应该将结果存储在临时变量中,并传递该变量(按值或引用)。

此外,请注意,ato...函数已经过时并且存在危险性,应该使用更好的错误处理能力的strtoll代替。

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


1
您可以使用复合字面量来分配临时数组,以便将值保存在内存中,而不是像其他答案建议的那样使用临时变量。
lst_new_element((long long[]){ atoll(argv[1]) }, sizeof(long long));

1
聪明,但由于编译器将分配一个不可见的变量,除了混淆之外没有任何收益(踩票不是我)。 - Paul Ogilvie
@PaulOgilvie 复合字面量只是在这里介绍的另一种方法。 - chux - Reinstate Monica

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