是否可以在C中创建通用函数?

时间:2013-08-08 20:04:08

标签: c arrays generics

我正在重新使用C,但我已经被其他语言的泛型所破坏了。我在可调整大小的数组的实现中已经使用了以下代码:

typdef struct {
  void** array;
  int length;
  int capacity;
  size_t type_size;
} Vector;

void vector_add(Vector* v, void* entry) {
  // ... code for adding to the array and resizing
}

int main() {
  Vector* vector = vector_create(5, sizeof(int));
  vector_add(vector, 4); // This is erroneous...
  // ...
}

在我尝试制作这个泛型时,我现在无法在向量中添加整数而不将其存储在其他地方的内存中。

有没有办法让这项工作(无论是原样,还是可能是更好的仿制药方法)?

8 个答案:

答案 0 :(得分:7)

对于我的回答,我假设您不熟悉内存部分(即使用内存池)。

  

在我试图制作这个通用的时候,我现在无法在向量中添加一个整数而不用将其存储在其他地方的内存中

如果要创建通用结构(就像你一样),那么你需要使用void指针。因此,从使用void指针开始,您需要将每个字段的值存储在内存池中,或者不常见地存储在堆栈中。注意,结构由void指针组成,因此内存地址包含在结构中,指向内存中值的其他位置。

如果在堆栈框架从调用堆栈中弹出一次就会在堆栈上声明它们时要小心,那些内存地址不被认为是有效的,因此可能被另一个堆栈帧使用(覆盖该集合中的现有值)内存地址)。

旁白:如果您迁移到C ++,那么您可以考虑使用C ++模板。

答案 1 :(得分:2)

您可以通过存储数据而不是指向它的指针来避免许多小的分配,例如

typedef struct {
  char* array;
  int length;
  int capacity;
  size_t type_size;
} Vector;

bool vector_add(Vector* v, void* entry)
{
    if (v->length < v->capacity || vector_expand(v)) {
        char* location = v->array + (v->length++)*(v->type_size);
        memcpy(location, entry, v->type_size);
        return 1;
    }
    return 0; // didn't fit
}

int main()
{
    Vector* vector = vector_create(5, sizeof(int));
    int value = 4;
    vector_add(vector, &value); // pointer to local is ok because the pointer isn't stored, only used for memcpy
}

答案 2 :(得分:2)

是;你可以拥抱Greenspun's Tenth Rule并在C中开发一个完整的动态语言,并在此过程中,开发一个相对干净的C运行时,可以在C内使用。

this project我做到了这一点,就像我之前的其他人一样。

在此项目的C运行时间内,将从C编号创建一个通用编号,如下所示:

val n = num(42);

由于val的表示方式,它只占用一个机器字。一些类型标签用于区分数字与指针,字符等。

还有:

val n = num_fast(42);

更快(一点操作宏),因为它没有进行任何特殊检查,数字42适合“fixnum”范围;它用于小整数。

将一个参数添加到向量的每个元素的函数可以写成(非常低效),如下所示:

val vector_add(val vec, val delta)
{
   val iter;
   for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
      val *pelem = vecref_l(vec, iter);
      *pelem = plus(*pelem, delta);
   }
   return nil;
}

由于plus是通用的,因此可以使用fixnums,bignums和reals以及字符,因为可以通过plus向字符添加整数位移。

类型不匹配错误将被较低级别的功能捕获并变为异常。例如,vec不是可以应用length的内容,length将会抛出。

带有_l后缀的函数会返回一个位置。其中vecref(v, i)返回向量i中偏移量v处的值,vecref_l(v, i)返回指向存储该值的向量中val类型位置的指针。 / p>

这都是C,只是ISO C规则有点弯曲:你不能在严格符合C的情况下有效地制作类似val的类型,但是你可以非常方便地使用你关心的架构和编译器关于支持。

我们的vector_add不够通用。可以做得更好:

val sequence_add(val vec, val delta)
{
   val iter;
   for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
      val elem = ref(vec, iter);
      refset(vec, iter, plus(elem, delta));
   }
   return nil;
}

通过使用通用refrefset,现在这也适用于列表和字符串,而不仅仅是矢量。我们可以这样做:

val str = string(L"abcd");
sequence_add(str, num(2));

str的内容将更改为cdef,因为2的位移已添加到每个字符中。

答案 3 :(得分:1)

你的想法可以做到:

int *new_int = (int*)malloc(sizeof(int));
*new_int = 4;
vector_add(vector, new_int);

当然,做一个int *create_int(int x)函数或类似的东西是个好主意:

int *create_int(int x)
{
    int *n = (int*)malloc(sizeof(int));
    *n = 4;
    return n;
}
//...
vector_add(vector, create_int(4));

如果您的环境允许,您可以考虑使用经过良好测试,广泛使用的库,它已经管理了所有这些,例如Glib。甚至是C ++。

答案 4 :(得分:1)

是的,这是我的一个实现(类似于你的)可能有所帮助。它使用的宏可以通过函数调用包装为立即值。

#ifndef VECTOR_H
# define VECTOR_H

# include <stddef.h>
# include <string.h>

# define VECTOR_HEADROOM 4

/* A simple library for dynamic
 * string/array manipulation
 *
 * Written by: Taylor Holberton
 * During: July 2013
 */

struct vector {
    void * data;
    size_t  size, len;
    size_t  headroom;
};

int vector_init (struct vector *);

size_t vector_addc  (struct vector *, int index, char c);
size_t vector_subc  (struct vector *, int index);

// these ones are just for strings (I haven't yet generalized them)
size_t vector_adds (struct vector *, int index, int iend, const char * c);
size_t vector_subs (struct vector *, int ibegin, int iend);

size_t vector_addi (struct vector *, int index, int i);
size_t vector_subi (struct vector *, int index);

# define vector_addm(v, index, datatype, element)                        \
do {                                                                    \
    if (!v) return 0;                                               \
                                                                    \
    if (!v->size){                                                  \
            v->data = calloc (v->headroom, sizeof (datatype));      \
            v->size = v->headroom;                                  \
    }                                                               \
                                                                    \
    datatype * p = v->data;                                         \
                                                                    \
    if (v->len >= (v->size - 2)){                                   \
            v->data = realloc (v->data,                             \
                    (v->size + v->headroom) * sizeof (datatype));   \
            p = v->data;                                            \
            memset (&p[v->size], 0, v->headroom * sizeof(datatype));\
            v->size += v->headroom;                                 \
    }                                                               \
                                                                    \
    if ((index < 0) || (index > v->len)){                           \
            index = v->len;                                         \
    }                                                               \
                                                                    \
    for (int i = v->len; i >= index; i--){                          \
            p[i + 1] = p[i];                                        \
    }                                                               \
                                                                    \
    p[index] = element;                                             \
                                                                    \
    v->len++;                                                       \
                                                                    \
} while (0)


# define vector_subm(v, index, datatype)                                 \
do {                                                                    \
    if (!v || !v->len){                                             \
            return 0;                                               \
    }                                                               \
                                                                    \
    if ((index < 0) || (index > (v->len - 1))){                     \
            index = v->len - 1;                                     \
    }                                                               \
                                                                    \
    datatype * p = v->data;                                         \
                                                                    \
    for (int i = index; i < v->len; i++){                           \
            p[i] = p[i + 1];                                        \
    }                                                               \
                                                                    \
    v->len--;                                                       \
                                                                    \
    if ((v->size - v->len) > v->headroom){                          \
            v->data = realloc (v->data, ((v->size - v->headroom) + 1) * sizeof (datatype));\
            v->size -= v->headroom;                                 \
    }                                                               \
                                                                    \
} while (0)

#endif

我通常将它们包裹起来:

size_t vector_addi (struct vector * v, int index, int i){
    vector_addm (v, index, int, i);
    return v->len;
}

我没有对此代码进行过审核,但我一直在我正在编写的大型程序中使用它,并且我没有遇到任何内存错误(使用valgrind)。

唯一真正缺失的东西(我一直想添加)从数组中添加和减去数组的能力。

编辑:我相信您也可以使用stdarg.h执行此类操作,但我从未尝试过。

答案 5 :(得分:1)

你问过更好的方法吗?这是:https://github.com/m-e-leypold/glitzersachen-demos/tree/master/generix/v0-2011(披露:这是我的代码)。

让我解释一下:

  • 我想要类型安全的通用容器(在其他语言中由适当的泛型(Ada)或参数多态(OCaml)提供。这是C中最缺少的功能。

    < / LI>
  • 宏不能做到(我不是 要详细解释一下。我只想说:模板扩展的结果或 泛型实例化应该是它自己的一个模块:在C中这意味着,有pre 分别导出的处理器符号可用于模块配置(如 -DUSE_PROCESS_QUEUE_DEBUGCODE)如果使用C宏生成,则无法执行此操作 实例

  • 我通过将元素大小和所有相关操作移动到描述性结构中来抽象元素类型。这将被传递给通用代​​码的每次调用。请注意,描述符描述了元素类型,因此每个通用实例都需要一个描述符实例。

  • 我正在使用模板处理器为通用代码创建一个瘦型安全前端模块。

示例:

这是检索元素的通用代码的原型:

void fifo_get ( fifo_DESCRIPTOR* inst, fifo* , void* var );

这是描述符类型:

typedef struct fifo_DESCRIPTOR {
  size_t maxindex;
  size_t element_size;
} fifo_DESCRIPTOR;

这是类型安全包装器模板中的模板代码:

<<eT>>  <<>>get  ( <<T>>* f ) { 
   <<eT>> e; fifo_get( &DESCRIPTOR, (fifo*) f, (void*) &e ); return e; 
}

这就是模板扩展器(实例化通用)从模板产生的内容:

float   floatq_get  ( floatq* f ) { 
    float e; fifo_get( &DESCRIPTOR, (fifo*) f, (void*) &e ); return e; 
}

所有这些都有一个很好的make集成,但在实例化中几乎没有任何类型安全性。使用cc进行编译时,只会出现每个错误。

我现在无法证明,为什么要在C中坚持使用源文本模板而不是迁移到C ++。对我来说,这只是一个实验。

问候。

答案 6 :(得分:1)

这种方法可能会吓到你,但如果你不需要任何类型专用逻辑,它可以工作:

// vector.h
#ifndef VECTOR_H
#define VECTOR_H

#define VECTOR_IMP(itemType) \
   typedef struct {          \
      itemType * array;      \
      int length;            \
      int capacity;          \
   } itemType##_Vector;      \
                             \
   static inline void itemType##_vector_add(itemType##_Vector* v, itemType v) { \
      // implementation of adding an itemType object to the array goes here     \
   }                                                                            \
                                                                                \
   [... other static-inline generic vector methods would go here ...]           \

// Now we can "instantiate" versions of the Vector struct and methods for
// whatever types we want to use.
VECTOR_IMP(int);
VECTOR_IMP(float);
VECTOR_IMP(char);

#endif

...以及一些调用代码示例:

#include "vector.h"

int main(int argc, char ** argv)
{
   float_Vector fv = {0};
   int_Vector iv = {0};
   char_Vector cv = {0};

   int_vector_add(&iv, 5);
   float_vector_add(&fv, 3.14f);
   char_vector_add(&cv, 'A');

   return 0;
}

答案 7 :(得分:0)

您可以只返回指向调用者可以存储它的位置的指针,而不是让向量类存储添加的对象:

typdef struct {
    char *buffer;
    size_t length;
    size_t capacity;
    size_t type_size;
} Vector;

void *vector_add(Vector* v)
{
    if (v->length == v->capacity) {
        // ... increase capacity by at least one
        // ... realloc buffer to capacity * type_size
    }
    return v->buffer + v->type_size * v->length++;
}

// in main:
*(int*)vector_add(v) = 4;