【问题标题】:C - Accept Argument of Known Size but Unknown Type [closed]C - 接受已知大小但未知类型的参数[关闭]
【发布时间】:2016-05-12 19:20:47
【问题描述】:

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

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

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

  2. 将数据的大小作为一个参数传递,调用堆栈上的数据本身作为一个额外的参数传递。 C 通过.../stdarg.h 方法支持未知数量、类型和大小的参数。我想我可以使用宏来获取 sizeof() 被附加的项目,并将其与项目本身一起传递给附加函数。这里的问题是可变参数宏要我指定一个类型(不仅仅是类型的大小)。所以,我做了一些挖掘,发现GCC显然使用__builtin_next_arg宏来实现stdarg.h中的变量参数。显然,这会使我的代码依赖于 GCC(或者至少依赖于这个特定的宏),但它至少可以与这个特定的编译器一起工作。据称,__builtin_next_arg 宏将参数列表中最后一个命名的参数的地址作为void* 给出。当我在 Windows(使用 MinGW 32 位)上尝试这种方法时,它按预期工作。从__builtin_next_arg 给出的值到新分配的缓冲区的简单memcpy() 复制了数据。然而,当我在 Ubuntu 上使用 GCC 64 位时,一切都乱了套。 __builtin_next_arg 给我的地址与我期望的论点相去甚远。编译器也开始偶尔抱怨va_start 的第二个参数不是列表中的最后一个参数,尽管我什至根本没有在我的代码中使用va_start。此外,无论我做什么,我得到的值似乎都是零(NULL0)。

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

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

【问题讨论】:

  • TL/DR。 ADT(抽象数据类型)是关键字。
  • @EugeneSh.:确实是 TL;DR。但模板只是饮食 ADT(还是“轻”?)。
  • 使用最适合您问题的语言。并具有适当的抽象级别。否则你很快就会失去兴趣和/或动力,迷失在细节中。如果您暂停数周/数月,不要忘记不了解您所做的事情。

标签: c gcc macros variadic-functions


【解决方案1】:

我建议您要么根本不使用 __builtin_* 函数,要么通过阅读您编写的简单函数的 docs/gcc 源代码/asm 编译器输出来彻底研究它们的实现,以了解它是如何工作的。

首先,由于使用了不同的ABImingwlinux 环境之间的特定 __builtin_next_arg 的行为可能完全不同。

接下来,__builtin_* 函数可能会被不同gcc 版本中的另一个函数所取代。例如,在 gcc-4.8.3(准确地说是 linux 构建)中,va_* 宏是使用 __builtin_va_start()__builtin_va_end()__builtin_va_arg() 函数,在 gcc 内部头文件中没有出现 __builtin_next_arg

【讨论】:

    【解决方案2】:

    不,您不能在此处使用__builtin_next_arg。原因是即使使用__builtin_next_arg 等,您的调用函数 也必须知道数据类型

    不,在 Linux x86-64 ABI 中,函数参数不存储在堆栈中;前 6 个参数在寄存器中提供。并且寄存器取决于它们的类型,所以floatint 在不同的寄存器中,即使它们的大小都是 4。抱歉,你不能这样做,即使在这个 ABI 上的特定 GCC 版本中。


    但是,如果您使用的是 C11,则可以使用 ,它使用 static_assert 来断言传入的 sizeof 对象;那么你可以使用一些_Generic 技巧将不同的类型存储到包含char contents[sizeof val];struct 中,例如:

    #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 的内容直接放入这个结构中。

    【讨论】:

      猜你喜欢
      • 2011-08-26
      • 1970-01-01
      • 2021-07-30
      • 1970-01-01
      • 2011-08-27
      • 1970-01-01
      • 1970-01-01
      • 2020-04-07
      • 1970-01-01
      相关资源
      最近更新 更多