纯 ANSI-C:制作通用数组

4

是否有可能在纯 ANSI-C 中复制一个通用数组?

我有这个结构体,它包含一个数组(目前是浮点数数组)和一些变量,如大小和容量,用于对数组进行更改。

typedef struct _CustomArray
{
    float* array; //the array in which the objects will be stored
    int size; //the current size of the array
    int capacity; //the max capacity of the array
} CustomArray; 

我使用这个结构体来创建一个纯C数组,可以添加/删除项目,动态扩展数组大小等等,所有标准数组的功能都有,只不过它是纯C制作的。 现在我想让初始化这个结构体时,可以设置它应该容纳的元素数据类型,目前它只能存储float类型的数据,但我想让它可以存储任何数据类型/其他结构体。但我不知道这是否可能。
此时用于创建此数组的函数为:
CustomArray* CustomArray_Create(int initCapacity, /*type elementType*/)
{
    CustomArray* customArray_ptr; //create pointer to point at the structure
    float* internalArray = (float*)malloc(sizeof(float) * initCapacity); //create the internal array that holds the items
    if(internalArray != NULL)
    {
        CustomArray customArray = { internalArray, 0, initCapacity }; //make the struct with the data
        customArray_ptr = &customArray; //get the adress of the structure and assign it to the pointer
        return customArray_ptr; //return the pointer
    }
    return NULL;
}

是否可能将数据类型作为参数传递,以便我可以动态地为该数据类型分配内存,并将其强制转换为给定的数据类型在数组中?

提前致谢,

Marnix van Rijswijk


不要以为在纯C中可以通过这种方式传递数据类型。查看支持异构数组列表的语言,例如C#,它仅适用于非基本数据类型,即类而不是int,float等。由于C不是面向对象的,因此您很可能无法获得此功能。 - Shamim Hafiz - MSFT
2
不要以下划线开头的标识符:这些名称保留给实现(编译器+libc);使用下划线和大写字母是双重错误的:这些名称在任何上下文中都被保留,因为它是新语言特性使用的内容(例如 _Pragma_Complex_Bool等);一个简单的解决方法是使用尾随下划线,这也可以与基于前缀的命名空间很好地配合使用。 - Christoph
1
网站上有许多关于如何在C语言中构建面向对象行为的问题:C中的面向对象编程你能否在C语言中编写面向对象代码? 等等。通过巧妙地使用sizeof和链接中讨论的函数指针机制,您可以实现所需的结果,但这将是一项更加艰巨的工作。 qsortbsearch的接口是一种折衷方案。 - dmckee --- ex-moderator kitten
2
试图将C编程语言变成一种不同的语言是一个误导性的追求。一旦你开始在C的基础之上编写自己的高级类型,并使用那些类型来编写所有的C代码,你不如从一开始就使用一种更高级的语言。事实上,你会遇到更多的问题,因为C++、ocaml等语言中的精密类型是由专家编写的,它们可能表现良好且没有错误...如果你想编写C程序,就要充分利用其效率。 - R.. GitHub STOP HELPING ICE
5个回答

8
您的代码存在严重问题...您正在返回局部变量(CustomArray)的地址,当函数返回该变量时,它将被销毁,因此您不能继续使用指针。您必须为该结构也分配空间,以便一旦函数返回,内存仍然可用。
关于将类型作为参数,您可以使用宏来实现类似的效果...例如使用以下内容:
#include <stdlib.h> 
#define DefArray(type) \
typedef struct T_##type##Array {\
    type *array; \
    int size, capacity; \
} type##Array; \
static type##Array *type##ArrayCreate(int capacity)\
{\
    type##Array *s = malloc(sizeof(type##Array));\
    if (!s) return NULL;\
    s->array = malloc(sizeof(type) * capacity);\
    if (!s->array) { free(s); return NULL; }\
    s->size=0; s->capacity = capacity;\
    return s;\
}

然后您可以这样使用它。
#include "customarray.h"
DefArray(float);
DefArray(double);

void foo()
{
    floatArray *fa = floatArrayCreate(100);
    ...
}

请注意,您需要使用宏来定义所有自定义函数。同时请注意,这种方法会在每个模块中复制代码(虽然我认为这并不是一个大问题,但如果您无法使用C++,那么您的目标平台可能相当小)。通过稍微复杂一些的方法,您可以为实现生成单独的.h文件和.c文件。

它能够正常工作,并向我展示了一种全新的做事方式。太棒了! - Marnix v. R.
3
欢迎进入元编程(编写能够生成代码的代码)的世界。C预处理器是一种非常薄弱的元编程形式,而C++的模板机制稍微好一点。如果您想要真正的魔法,您需要使用外部生成器(例如,在Python/Perl中编写C/C++生成器很容易),或者转移到其他支持严格元编程的语言(例如Lisp)。 - 6502

3
男孩,这真的需要用C++来完成。
我认为在C中最接近的方法是不传递类型,而是传递大小(sizeof(type))。
你可以让你的函数更通用,以便在它只知道数组中每个项的大小时,它可以实现所需的功能。这就是像bsearch()这样的函数的工作方式。

谢谢,我知道这更适合用C++来完成。但我想知道是否有可能。可惜的是不行。 :[ - Marnix v. R.
仅凭大小来推断数据类型是否安全? - Shamim Hafiz - MSFT
Gunner:可以对每个项目使用多少内存做出安全的假设。这应该足以分配和移动内存。唯一的问题是如果数据类型是指针。在这种情况下,指向的项目也应该被复制。但对于所有基本类型,这种方法是完全有效的。 - Jonathan Wood
Marnix:我真的不认为这是个问题。只要你的基本数据类型不涉及指针,我所描述的方法就可以正常工作。因此,你可以调用malloc(size * initCapacity)而不是你的函数调用malloc(sizeof(float) * initCapacity)。如果这样行得通,请将我的回答标记为答案。 - Jonathan Wood
谢谢你的建议,我会尝试这种方法,如果有效的话会标记你的回复。 - Marnix v. R.

2
实现这一点的一种方法是使用所谓的X-macros这里是一个(可能有缺陷的)通用向量实现,使用了这种技术。
然后它被用作
// defining generic parameters
#define PREFIX tv
#define ITEM token
#define NAME token_vector
#include "vector.h"

...
token_vector tv = tv_new(100);
*(tv.front) = some_token;
tv_push_back(&tv, other_token);

0
所以,对于没有自己声明类型的对象,有一个“有效类型”的概念。(事实上,这些对象基本上只包括“*alloc指针的另一端”和几个奇怪的联合规则) 基本上,这种对象的“有效类型”是你最后用来赋值给它的任何东西,不算那些是charchar[]的次数,因为原因。
其中一个有趣的交互与声明结构类型的规则有关。也就是说,你可以自由地重新声明相同的标签名(或缺乏标签名),每个声明都引入了一个全新的类型(过去的名称被遮蔽,但具有旧类型的对象不会被重新解释)。
所以你可以做这样的事情:
# define DECL_VECTOR(NAME,TYPE,SIZE) PUN_STRUCT(NAME,TYPE,SIZE)  INIT_STRUCT(NAME,TYPE,SIZE) 

# define PUN_SIZE sizeof(void*)+sizeof(int)*2


# define PUN_STRUCT(NAME,TYPE,SIZE)                      \
   struct {                                              \
      TYPE (*p)[(SIZE)];                                 \
      int size;                                          \
      int capacity;                                      \
   } *NAME = malloc(PUN_SIZE);                        


# define INIT_STRUCT(NAME,TYPE,SIZE)  do {               \
   if (!NAME) {                                          \
        perror("malloc fail");                           \
        abort();                                         \
   }else {                                               \
        NAME->size = (SIZE);                             \
        NAME->capacity = 0;                              \
        NAME->p = malloc(sizeof(*NAME->p));              \
        if (!NAME->p) {                                  \
            perror("malloc fail");                       \
            abort();                                     \
        }                                                \
        NAME->p = (TYPE(*)[(SIZE)])(NAME->p);            \
   }                                                     \
   }while(false)


int main(int argc, char *argv[]) 
 {

   DECL_VECTOR(vec1,int,8);


    printf("size of handle struct:  %zu,\n\
size of vector array:   %zu,\n\
size of vector element: %zu\n\
address of handle struct: %p,\n\
address of vector array:  %p\n",  sizeof(*vec1),       \
                                  sizeof(*vec1->p),    \
                                  sizeof(*vec1->p[0]), \
                                  vec1,                \
                                  vec1->p);
   free(vec1->p);
   free(vec1);
   return 0;
 }

(然而,人们可能会指责您滥用宏权限,他们可能并不完全错误)


0

几年前,我在C语言中尝试了一下泛型编程,只是为了好玩。

基本上,我最终利用了预处理器。我想我还算有些成功:我确实为几个最重要的通用数据结构实现了一些宏符号。

但我绝对没有以任何自动方式递归运行这些宏——也就是说,创建一个数组或哈希表的数组等等。这是由于C预处理器宏的有趣疯狂语义所致。

如果你感兴趣,这里是代码链接: https://github.com/christianfriedl/CGenerics/blob/master/src/cgArray.h


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接