使C动态数组通用

 黄梓恒_ 发布于 2023-02-08 15:56

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

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

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

我是不是该 :

    为我想要使用的每种类型复制/粘贴一次库(可怕,但它会工作并且效率很高)

    使它成为一个巨大的宏,我可以扩展我想要的类型(因此它将具有与1相同的效果,但有点优雅和可用)

    将尖头元素的大小变为动态数组结构的一部分.将主要工作,但函数采取和直接返回动态数组类型将有问题.void*并不总是一个可行的选择

    放弃这个想法,并在我需要这些高级功能时使用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 
#include 
#include 

// 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)

小智.. 10

我要做的是回到预处理器的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];

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

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];
    

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

    2023-02-08 15:59 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有