【问题标题】:Making C dynamic array generic使 C 动态数组泛型
【发布时间】:2014-01-11 04:04:00
【问题描述】:

我刚刚写了一个很好的库,它可以很好地处理在 C 中分配在堆上的动态数组。它支持许多操作,使用起来相对简单,“感觉”几乎就像一个普通的旧数组一样。也很容易基于它来模拟很多数据结构(栈、队列、堆等……)

它可以处理任何类型的数组,但问题是每次编译只有一个类型。 C 没有模板,因此不可能在同一程序中拥有例如动态整数数组和动态字符数组,这是一个问题。

我还没有找到任何真正的解决方案,我发现的所有内容都涉及 void*,不,我不想要一个 void 指针数组。能够放置指针很好,但我也希望能够拥有一个原始数据类型的数组。 (例如,您可以添加 3,然后像这样访问它:array.data[i]

我应该:

  1. 为我想使用的每种类型复制/粘贴一次库(很糟糕,但它会起作用并且很高效)

  2. 把它做成一个巨大的宏,我可以用我想要的类型进行扩展(所以它的效果和 1 一样,但是有点优雅和实用)

  3. 将指向元素的大小设置为变量,该变量是动态数组结构的一部分。大多数情况下都可以工作,但是直接获取和返回动态数组类型的函数会出现问题。 void* 并不总是一个可行的选择

  4. 当我需要这些高级功能时,放弃这个想法并改用 C++

图书馆的工作方式是这样的:用法

/* Variable length array library for C language
 * Usage :
 * Declare a variable length array like this :
 *
 * da my_array;
 *
 * Always initialize like this :
 * 
 * da_init(&da);             // Creates a clean empty array
 *
 * Set a length to an array :
 *
 * da_setlength(&da, n);     // Note : if elements are added they'll be uninitialized
 *                             // If elements are removed, they're permanently lost
 *
 * Always free memory before it goes out of scope (avoid mem leaks !)
 *
 * da_destroy(&da);
 *
 * Access elements much like a normal array :
 *   - No boundary checks :           da.data[i]
 *   - With boundary checks (debug) : da_get(data, i)
 *
 * da.length;    // Return the current length of the variable length array (do NOT set the length by affecting this !! Use da_setlength instead.)
 *
 * You can add single elements at the end and beginning of array with
 *
 * da_add(&da, value);       // Add at the end
 * da_push(&da, value);      // Add at the front
 *
 * Retrieve values at the end and front of array (while removing them) with
 *
 * da_remove(&da);          // From the end
 * da_pop(&da);             // From the front
 *
 * Concatenate it with a standard array or another variable length array of same type with
 *
 * da_append(&da, array, array_length);  // Standard array
 * da_append(&da, &db);                 // Another variable length array
 */

实施(对不起,它很大,但为了问题的完整性,我必须给出它)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// Increment by which blocks are reserved on the heap
#define ALLOC_BLOCK_SIZE 16

// The type that the variable length array will contain. In this case it's "int", but it can be anything really (including pointers, arrays, structs, etc...)
typedef int da_type;

// Commend this to disable all kinds of bounds and security checks (once you're sure your program is fully tested, gains efficiency)
#define DEBUG_RUNTIME_CHECK_BOUNDS

// Data structure for variable length array variables
typedef struct
{
    da_type *start; // Points to start of memory allocated region
    da_type *data;      // Points to logical start of array
    da_type *end;       // Points to end of memory allocated region
    size_t length;      // Length of the array
}
da;

// Initialize variable length array, allocate 2 blocks and put start pointer at the beginning
void da_init(da *da)
{
    da_type *ptr = malloc(ALLOC_BLOCK_SIZE * sizeof(da_type));
    if(ptr == 0) exit(1);
    da->start = ptr;
    da->data = ptr;
    da->end = da->start + ALLOC_BLOCK_SIZE;
    da->length = 0;
}

// Set the da size directly
void da_setlength(da *da, size_t newsize)
{
    if(newsize % ALLOC_BLOCK_SIZE != 0)
        newsize += ALLOC_BLOCK_SIZE - newsize % ALLOC_BLOCK_SIZE;

    ptrdiff_t offs = da->data - da->start;
    da_type *ptr = realloc(da->start, newsize * sizeof(da_type));
    if(!ptr) exit(1);

    da->start = ptr;
    da->data = ptr + offs;
    da->end = ptr + newsize;
    da->length = newsize;
}

// Destroy the variable length array (basically just frees memory)
void da_destroy(da* da)
{
    free(da->start);
#ifdef DEBUG_RUNTIME_CHECK_BOUNDS
    da->start = NULL;
    da->data = NULL;
    da->end = NULL;
    da->length = 0;
#endif
}

// Get an element of the array with it's index
#ifdef DEBUG_RUNTIME_CHECK_BOUNDS
    //Get an element of the array with bounds checking
    da_type da_get(da *da, unsigned int index)
    {
        if(index >= da->length)
        {
            printf("da error : index %u is out of bounds\n", index);
            exit(1);
        }
        return da->data[index];
    }

    //Set an element of the array with bounds checking
    void da_set(da *da, unsigned int index, da_type data)
    {
        if(index >= da->length)
        {
            printf("da error : index %u is out of bounds\n", index);
            exit(1);
        }
        da->data[index] = data;
    }
#else
    //Get an element of the array without bounds checking
    #define da_get(da, index) ((da)->data[(index)])

    //Set an element of the array without bounds checking
    #define da_set(da, index, v) (da_get(da, index) = v)
#endif


// Add an element at the end of the array
void da_add(da *da, da_type i)
{   // If no more memory
    if(da->data + da->length >= da->end)
    {   // Increase size of allocated memory block
        ptrdiff_t offset = da->data - da->start;
        ptrdiff_t newsize = da->end - da->start + ALLOC_BLOCK_SIZE;
        da_type *ptr = realloc(da->start, newsize * sizeof(da_type));
        if(!ptr) exit(1);

        da->data = ptr + offset;
        da->end = ptr + newsize;
        da->start = ptr;
    }
    da->data[da->length] = i;
    da->length += 1;
}

// Remove element at the end of the array (and returns it)
da_type da_remove(da *da)
{
#ifdef DEBUG_RUNTIME_CHECK_BOUNDS
    if(da->length == 0)
    {
        printf("Error - try to remove item from empty array");
        exit(1);
    }
#endif
    //Read last element of the array
    da->length -= 1;
    da_type ret_value = da->data[da->length];
    //Remove redundant memory if there is too much of it
    if(da->end - (da->data + da->length) > ALLOC_BLOCK_SIZE)
    {
        ptrdiff_t offset = da->data - da->start;
        ptrdiff_t newsize = da->end - da->start - ALLOC_BLOCK_SIZE;
        da_type *ptr = realloc(da->start, newsize * sizeof(da_type));
        if(!ptr) exit(1);

        da->data = ptr + offset;
        da->end = ptr + newsize;
        da->start = ptr;
    }
    return ret_value;
}

// Add element at the start of array
void da_push(da *da, da_type i)
{   //If array reaches bottom of the allocated space, we need to allocate more
    if(da->data == da->start)
    {
        ptrdiff_t newsize = da->end - da->start + ALLOC_BLOCK_SIZE;
        da_type *ptr = realloc(da->start, newsize * sizeof(da_type));
        if(!ptr) exit(1);
        memmove(ptr + ALLOC_BLOCK_SIZE, ptr, da->length * sizeof(da_type));

        da->data = ptr + ALLOC_BLOCK_SIZE;
        da->start = ptr;
        da->end = ptr + newsize;
    }
    // Store element at start of array
    da->length += 1;
    da->data -= 1;
    da->data[0] = i;
}

//Remove 1st element of array (and return it)
da_type da_pop(da *da)
{
#ifdef DEBUG_RUNTIME_CHECK_BOUNDS
    if(da->length == 0)
    {
        printf("Error - try to remove item from empty array");
        exit(1);
    }
#endif
    da_type ret_value = da->data[0];
    da->length -= 1;
    da->data += 1;
    ptrdiff_t offset = da->data - da->start;
    if(offset > ALLOC_BLOCK_SIZE)
    {
        ptrdiff_t newsize = da->end - da->start - ALLOC_BLOCK_SIZE;
        da_type *ptr = realloc(da->start, newsize * sizeof(da_type));
        if(!ptr) exit(1);
        memmove(ptr + offset - ALLOC_BLOCK_SIZE, ptr + offset, da->length * sizeof(da_type));

        da->data = ptr + offset - ALLOC_BLOCK_SIZE;
        da->start = ptr;
        da->end = ptr + newsize;
    }
    return ret_value;
}

// Append array t to s
void da_array_append(da *s, const da_type *t, size_t t_len)
{
    if((s->length + t_len) > (s->end - s->start))
    {   // Should reserve more space in the heap
        ptrdiff_t offset = s->data - s->start;
        ptrdiff_t newsize = s->length + t_len;
        // Guarantees that new size is multiple of alloc block size
        if(t_len % ALLOC_BLOCK_SIZE != 0)
            newsize += ALLOC_BLOCK_SIZE - (t_len % ALLOC_BLOCK_SIZE);

        da_type *ptr = malloc(newsize * sizeof(da_type));
        if(!ptr) exit(1);

        memcpy(ptr, s->data, s->length * sizeof(da_type));
        memcpy(ptr + s->length, t, t_len * sizeof(da_type));
        free(s->start);
        s->data = ptr;
        s->start = ptr;
        s->end = ptr + newsize;
    }
    else
        // Enough space in heap buffer -> do it the simple way
        memmove(s->data + s->length, t, t_len * sizeof(da_type));

    s->length += t_len;
}

// Append a da is a particular case of appending an array
#define da_append(s, t) da_array_append(s, (t)->data, (t)->length)

【问题讨论】:

    标签: c arrays generics dynamic


    【解决方案1】:

    我要做的是回退到预处理器hackage。您绝对可以通过在必要时添加类型信息来实现类似 C++ 中的模板。

    struct da_impl {
        size_t len;
        size_t elem_size;
        size_t allocsize; // or whatever
    };
    
    void da_init_impl(struct da_impl *impl, size_t elem_size)
    {
        impl->len = 0;
        impl->elem_size = elem_size;
        impl->allocsize = 0;
    }
    
    #define DA_TEMPLATE(t) struct { da_impl impl; t *data; }
    #define da_init(a) da_init_impl(&a.impl, sizeof(*a.data))
    
    // etc.
    

    那么你可以这样使用它:

    DA_TEMPLATE(int) intArray;
    da_init(intArray);
    
    da_append(intArray, 42);
    
    int foo = intArray.data[0];
    

    一个缺点是这会创建一个匿名结构,您不能真正在其范围之外使用它,但也许您可以忍受...

    【讨论】:

    • @FiddlingBits :) 不幸的是,尽管“其他人”(khm ......)的反对票即将到来......:/
    • 当你笑着去银行的时候......一些声望点并不重要。 :-D
    • 好的,这几乎就是我想要的。很高兴 DA_struct 在主程序中具有您想要的真实类型(以便取消引用工作),并且可以调用具有大小作为宏隐藏的额外参数的函数。这是一个天才的想法。但是,我仍然对直接获取和返回 da 类型值的函数(即 add、remove、push 和 pop 函数)有问题。我可以将指针传递给 void(并在 da 中复制正确的字节数),并摆脱返回值(这不是必需的),但它仍然不是很优雅,也不是我想要的。跨度>
    • @user2537102 C 语言提供的功能远不止这些。如果您对 C 可以处理的抽象级别不满意,请切换到 C++。有些功能(例如模板)根本无法在 C 中完成。只是没有。期间。
    • 好吧,这不是问题,你刚刚告诉我可以用纯 C 语言做到的最好的,所以我被指示了。
    【解决方案2】:

    为您的通用数据使用联合...

    typedef union
    {
        int *pIntArr;
        double *pDblArr;
        struct *pStructArr;
        ... // Etc.
    } genericData;
    

    ...并使用结构来保存它,这样您还可以包含通用数据联合所保存的数据及其长度。

    typedef struct
    {
        genericData data;
        int dataType;   // 0 == int, 1 == double, 2 == etc.
        int dataLength; // Number of elements in array
    } genericDataType;
    

    【讨论】:

    • 如果你想存储一个非常大的结构怎么办?如果存储较小的对象会浪费内存。此外,您的解决方案需要稍后在代码中进行切换。
    • @self。不知道你说的浪费内存是什么意思。
    • 具有 char 指针和大小为 1000 的 char 数组的联合有多大?
    • @self。没有“内存浪费”——联合包含指针。这并不是说有一系列工会。有一个指向不同类型数组(的第一个元素)的指针的联合。这有很大的不同。 (但即使是你声称的情况——何必呢?现在内存很便宜。除非这显然导致大量内存被多余地保留,否则不值得担心联合的大小。)
    • sizeof(genericData); 将始终相同。当然,如果你创建大数据类型的大数组,那会消耗更多的内存,但如果你想存储它,就没有办法了。
    猜你喜欢
    • 2017-05-06
    • 2017-02-09
    • 2012-10-14
    • 2020-05-24
    • 2018-10-24
    • 2013-04-19
    • 1970-01-01
    • 2018-08-29
    • 1970-01-01
    相关资源
    最近更新 更多