我有兴趣在C中创建我自己的双向链表实现。目标是使其变得灵活,并且"用户"尽可能友善。这意味着它不能限于一种类型的数据。我还希望尽量减少列表代码之外的内存管理。也就是说,我希望列表代码能够处理分配和释放任何必要的内存。 (但是,当然,该列表可用于存储指向动态分配数据的指针。)
我使用了两个结构。 "节点" struct保存指向它之前的节点和紧随其后的节点的指针,指向它包含的数据的void*
指针以及数据的大小。但是,因为这是C,所以它不能保持数据类型。 "列表" struct跟踪列表的开头和结尾,列表中元素的数量,等等。我已经实现了初始化列表和将数据附加到列表的功能。内存分配和释放和链接似乎工作正常,列表似乎正确链接到彼此。问题是如何在创建列表节点时实际导入数据。以下是我考虑过的方法:
通过void*
传递指向数据的指针,并将大小作为另一个参数传递。通过添加宏来获取变量的地址和大小并将其传递给函数,可以使这更加用户友好。问题?并非我想要添加到列表中的所有内容都可以使用其地址。例如,考虑list_append(list, 17)
。这应该在列表末尾添加一个值为17的整数有效负载的新节点,但它不起作用,因为整数文字17
无法获取其地址。
将数据大小作为一个参数传递,将数据本身作为额外参数传递给调用堆栈。 C通过...
/ stdarg.h
方法支持未知数量,类型和大小的参数。我想我可以使用宏来获取附加项的sizeof()
,并将其与项目本身一起传递给附加函数。这里的问题是varargs宏要我指定一个类型(不仅仅是类型的大小)。所以,我做了一些挖掘,发现GCC显然使用__builtin_next_arg
宏来实现stdarg.h
中的变量参数。显然,这会使我的代码依赖于GCC(或者至少在这个特定的宏上),但它至少可以用于这个特定的编译器。据称,__builtin_next_arg
宏将参数列表中最后一个命名的参数的地址作为void*
。当我在Windows上尝试这种方法(使用MinGW 32位)时,它按预期工作。从memcpy()
给出的值到新分配的缓冲区的简单__builtin_next_arg
复制了数据。但是,当我在Ubuntu上使用GCC 64位时,一切都崩溃了。 __builtin_next_arg
给了我很远的地址,远离我预期的论点。编译器也开始偶尔抱怨va_start
的第二个参数不是列表中的最后一个参数,尽管事实上我甚至根本不在我的代码中使用va_start
。此外,无论我做了什么,我似乎得到的值都是零(NULL
,0
)。
有没有办法解决这个问题?我基本上想要的是va_arg
的一个版本,它给出了堆栈中参数的地址。其他方法也是可以接受的。
在C ++中,我可以使用模板完全避免这个问题,但我想使用C。
答案 0 :(得分:1)
我建议您不要使用__builtin_*
函数,或者通过阅读docs / gcc sources / asm编译器输出您编写的简单函数来彻底研究它们的实现。
首先,mingw
和linux
环境之间特定__builtin_next_arg的行为可能完全不同,因为使用的ABI
不同。
接下来,__builtin_*
函数可能被另一个gcc
版本中的另一个函数取代。例如,在gcc-4.8.3(确切地说是linux build)中使用了va_ * macroses
__builtin_va_start()
,__builtin_va_end()
和__builtin_va_arg()
个函数,__builtin_next_arg
内部头文件中没有出现gcc
。
答案 1 :(得分:0)
不,你不能在这里使用__builtin_next_arg
。原因是,即使使用__builtin_next_arg
等,您的称为函数也必须知道数据类型。
不,函数参数不存储在堆栈中,比如Linux x86-64 ABI;前6个参数在寄存器中提供。寄存器取决于它们的类型,因此float
和int
位于不同的寄存器中,即使它们都是4号。抱歉,即使在特定的GCC版本中,你也不能这样做。这个ABI。
但是,如果您使用的是C11,则可以使用宏,使用static_assert
来声明传入的sizeof
对象;然后,您可以使用一些_Generic
技巧将不同类型存储到包含struct
的{{1}}中,例如:
char contents[sizeof val];
这是非常便携的,但请注意,例如MSVC编译器甚至不支持C99。 GCC和LLVM不应该有问题。但是,另外,您必须手动添加支持的每种兼容类型。包括每个指针类型,或者将指针投射到说#include <assert.h>
#include <string.h>
#define VALUE_SIZE (sizeof(int))
struct list;
struct value_type {
char contents[VALUE_SIZE];
};
int _list_append(struct list *l, struct value_type v);
struct value_type coerce_float(float arg) {
struct value_type rv;
memcpy(&arg, rv.contents, sizeof arg);
return rv;
}
struct value_type coerce_int(int arg) {
struct value_type rv;
memcpy(&arg, rv.contents, sizeof arg);
return rv;
}
static_assert(sizeof(float) == VALUE_SIZE,
"code relies on float and int being of the same size");
#define coerce_arg(X) (_Generic((X), \
float: coerce_float, \
int: coerce_int, \
void*: coerce_int \
)((int)X))
#define list_append(L, X) _list_append(L, coerce_arg(X))
list_append(l, 4);
list_append(l, 4.0f);
list_append(l, (void*)0); // will throw an error since it is not supported
。
如果您只使用GCC,我相信您可以编写一个使用{{3}}创建精确参数类型的变量的宏,然后(void*)
将其内容直接添加到此结构中