C - 接受已知大小但未知类型的参数

时间:2016-05-12 19:20:48

标签: c gcc macros variadic-functions

我有兴趣在C中创建我自己的双向链表实现。目标是使其变得灵活,并且"用户"尽可能友善。这意味着它不能限于一种类型的数据。我还希望尽量减少列表代码之外的内存管理。也就是说,我希望列表代码能够处理分配和释放任何必要的内存。 (但是,当然,该列表可用于存储指向动态分配数据的指针。)

我使用了两个结构。 "节点" struct保存指向它之前的节点和紧随其后的节点的指针,指向它包含的数据的void*指针以及数据的大小。但是,因为这是C,所以它不能保持数据类型。 "列表" struct跟踪列表的开头和结尾,列表中元素的数量,等等。我已经实现了初始化列表和将数据附加到列表的功能。内存分配和释放和链接似乎工作正常,列表似乎正确链接到彼此。问题是如何在创建列表节点时实际导入数据。以下是我考虑过的方法:

  1. 通过void*传递指向数据的指针,并将大小作为另一个参数传递。通过添加宏来获取变量的地址和大小并将其传递给函数,可以使这更加用户友好。问题?并非我想要添加到列表中的所有内容都可以使用其地址。例如,考虑list_append(list, 17)。这应该在列表末尾添加一个值为17的整数有效负载的新节点,但它不起作用,因为整数文字17无法获取其地址。

  2. 将数据大小作为一个参数传递,将数据本身作为额外参数传递给调用堆栈。 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 。此外,无论我做了什么,我似乎得到的值都是零(NULL0)。

  3. 有没有办法解决这个问题?我基本上想要的是va_arg的一个版本,它给出了堆栈中参数的地址。其他方法也是可以接受的。

    在C ++中,我可以使用模板完全避免这个问题,但我想使用C。

2 个答案:

答案 0 :(得分:1)

我建议您不要使用__builtin_*函数,或者通过阅读docs / gcc sources / asm编译器输出您编写的简单函数来彻底研究它们的实现。

首先,mingwlinux环境之间特定__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个参数在寄存器中提供。寄存器取决于它们的类型,因此floatint位于不同的寄存器中,即使它们都是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*)将其内容直接添加到此结构中