【问题标题】:C: Nicer syntax for generic data structures using void pointers?C:使用 void 指针的通用数据结构的语法更好?
【发布时间】:2012-04-08 12:36:00
【问题描述】:

我目前正在用 C 语言编写一个堆栈实现。我需要它使用尽可能少的内存并尽可能快,同时仍然采用我可以扔给它的每种数据类型,每个堆栈中有多种类型。我决定为此使用 void 指针,尽管我的 C 生锈了,但它的工作速度相对较快。然而,实际上使用它是相当难看的。

为了测试堆栈,我使用循环推送整数。实际上将 int 作为 void 指针传递是问题所在。

我的第一个想法是使用&

for (int i = 0; i < 20; i++){
    stack_push(s, &i); //s is the stack_t pointer
}

但是,如果仔细检查,这显然是行不通的,因为i 每一步都会破坏性地更新。堆栈中的每个元素都以20 结尾。

我后来转向了一个非常丑陋的解决方案:

for (int i = 0; i < 20; i++){
    int* p = malloc(sizeof(void*));
    *p = i;
    stack_push(s, p);
}

有效,但存在两个问题:

  1. 很丑

  2. 我不得不担心堆栈中每个元素的内存管理。 (出于某种原因,手动遍历堆栈并释放每个元素仍然会泄漏内存......)

有没有更好的方法来做到这一点,而不用联合浪费不必要的内存并且仍然很快?谢谢。

【问题讨论】:

  • 当你从堆栈中弹出项目时,你怎么知道你刚刚取出了什么?您在每个堆栈中都提到了多种类型,但仅凭一个指针就无法判断它是什么类型的项目。
  • 堆栈的用途是什么?粗略地说,堆栈释放内存的速度很快,因为您只需将堆栈指针向下移动即可。但是如果栈只包含指向堆的指针,仍然需要一个一个地释放,那么栈仍然是最好的解决方案吗?
  • 我不确定你在这里问什么。您推送的指针通常不是指向简单整数的指针,而是指向某些类型对象的有效指针。调用者不是负责管理这些对象的内存吗?或者您是否正在考虑一个堆栈实现,它对指针负责并在堆栈被销毁时释放它们?
  • @Douglas 这是一种基于堆栈的语言的实现,所以我在数据结构类型上没有太多选择——它至少需要是可推送的、可查看的和可弹出的
  • @NiklasB。是的,如果需要的话,我想要一些我可以直接将对象放入其中的东西。

标签: c syntax void void-pointers


【解决方案1】:

如果我没有误解,您想要的是一个堆栈,您可以在其中放置您想要的每种类型的数据。这是“struct any”的想法。我认为这会有所帮助。

union multitype
{
    int     asInt;
    void    *asVPtr;
    TYPE_A  *asA;
    TYPE_B  *asB;
    ...
}

typedef struct _any {
    int              type;
    union multitype  value;
} Any;

void any_put_int(Any *pAny, int value);
void any_put_v_ptr(Any *pAny, void *value);
void any_put_typeA_ptr(Any *pAny, TYPE_A *value);
void any_put_typeB_ptr(Any *pAny, TYPE_B *value);
...

int     any_get_int(Any *pAny);
void*   any_get_v_ptr(Any *pAny);
TYPE_A* any_get_typeA_ptr(Any *pAny);
TYPE_B* any_get_typeB_ptr(Any *pAny);
...

如果我们得到一个基于 struct Any 的堆栈实现,问题中的代码将是这样的。

for (int i = 0; i < 20; i++){
    Any anAny;
    any_put_int(&anAny, i);
    stack_push(s, anAny); // or stack_push(s, &anAny);
}

【讨论】:

  • 我对联合的问题是(据我所知)编译器会将所有类型填充到最大类型的大小。 4 或任何字节对于一些元素来说并不是什么大问题,但对于 1000+ 来说绝对不好玩。
  • @Rotten194 。在大多数情况下,struct Any 占用 8 个字节的内存,4 个字节用于类型,4 个字节用于数据地址。请注意,联合多类型中的类型长度相等,它们是指针,除了一个是整数。您需要维护大多数数据类型的内存。将整个数据放入堆栈将花费两个内存副本,当数据很大时,它也不好玩。除非数据很小,否则容器最好保存数据本身以外的地址。堆栈VS中的每个对象额外4个字节重复数据和额外的内存副本,这更好取决于实际情况。
【解决方案2】:

如果要混合类型,则需要知道类型是什么。

如果堆栈的客户端(或用户)将类型转换回正确的形式,那很好,但堆栈的大小仍然需要可用。

如果调用者也知道这一点,界面可能如下所示:

void push(const int size, void* value); void pop(const int size, void** valueptr);

我认为这通常不会很健壮,但如果您正在编写一个编译器,那么它可以生成所有正确的代码,就像 C 编译器一样。

在内部,我有两个版本,一个用于调试,它保留大小的值,一个用于“性能”,它不保留。我先看看调试。

我会放弃指针的想法,因为

  1. 它使用了额外的空间
  2. 分配的空间需要由某物来管理。

如果堆栈存储值,那么一旦弹出它就没有责任。

一个实现:

unsigned char space[BIG];
unsigned char *stack = &space[0];
int top = 0;
void push(const int size, void* value) {
    unsigned char* valp = (char *)value;
    *(int *)stack = size;
    stack += sizeof(int);
    for (int i=0; i<size; ++i) {
       *stack++ = *val++;
    }
}

pop 是相反的,

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-13
    • 1970-01-01
    • 2016-08-05
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    相关资源
    最近更新 更多