Closed. This question needs to be more
focused。它当前不接受答案。
想改善这个问题吗?更新问题,使其仅通过
editing this post专注于一个问题。
3年前关闭。
我有兴趣在C中创建自己的双向链接列表实现。目标是使其尽可能灵活和“用户”友好。这意味着它不能仅限于一种数据类型。我还想最小化列表代码之外的内存管理。也就是说,我希望列表代码能够处理分配和释放任何必要的内存。 (但是,当然,该列表可用于存储指向动态分配的数据的指针。)
我正在使用两个结构。 “节点”结构保留指向紧接在其之前的节点和紧随其后的节点的指针,指向其包含的数据的
void*
指针以及数据的大小。但是,由于它是C,因此无法保存数据类型。 “列表”结构跟踪列表的开头和结尾,列表中元素的数量,等等。我已经实现了初始化列表并将数据附加到列表的功能。内存分配,释放和链接似乎运行良好,并且列表似乎可以正确地相互链接。问题是在创建列表节点时如何实际导入数据。这是我考虑过的方法:
通过
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。
不,您不能在此处使用__builtin_next_arg
。原因是即使使用__builtin_next_arg
之类的,被调用函数也必须知道数据的类型。
不,函数参数未存储在Linux x86-64 ABI中;前6个自变量在寄存器中提供。寄存器取决于它们的类型,因此float
和int
都在不同的寄存器中,即使它们的大小均为4。对不起,即使在此ABI上的特定GCC版本中,您也无法做到这一点。
但是,如果您使用的是C11,则可以使用一个宏,该宏使用static_assert
声明传入的sizeof
对象。那么您可以使用一些_Generic
技巧将不同类型存储到包含struct
的char contents[sizeof val];
中,例如:
#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
这是非常可移植的,但是请注意,例如MSVC编译器甚至不支持C99。 GCC和LLVM应该没有问题。但是,另一方面,您必须手动添加每个受支持的兼容类型。包括每种指针类型,或将指针强制转换为
(void*)
。
如果您可以只使用GCC,我相信您可以编写一个使用
typeof
来使变量具有确切参数类型的宏,然后将
memcpy
其内容直接放入此结构中。