C动态增长阵列

Bal*_*nia 120 c dynamic-arrays

我有一个程序读取游戏中实体的"原始"列表,我打算创建一个数组,其中包含一个不确定数量的实体的索引号(int),用于处理各种事物.我想避免使用太多的内存或CPU来保存这些索引......

到目前为止,我使用的一个快速而肮脏的解决方案是在主处理函数(本地焦点)中声明具有最大游戏实体大小的数组,以及另一个整数来跟踪已添加到列表中的数量.这并不令人满意,因为每个列表都拥有3000多个阵列,这并不是那么多,但感觉就像是浪费,因为我可以使用6-7列表的解决方案来实现不同的功能.

我没有找到任何C(不是C++或C#)特定的解决方案来实现这一目标.我可以使用指针,但我有点害怕使用它们(除非它是唯一可能的方式).

数组不会离开本地函数作用域(它们将被传递给函数,然后被丢弃),以防更改内容.

如果指针是唯一的解决方案,我如何跟踪它们以避免泄漏?

cas*_*nca 194

我可以使用指针,但我有点害怕使用它们.

如果需要动态数组,则无法转义指针.你为什么害怕?他们不会咬人(只要你小心,那就是).在C中没有内置的动态数组,你只需要自己编写一个.在C++中,您可以使用内置std::vector类.C#和几乎所有其他高级语言也有一些类似的类来管理动态数组.

如果你打算自己编写,这里有一些东西可以帮助你入门:大多数动态数组实现都是从一些(小的)默认大小的数组开始,然后每当你在添加一个新元素时用完空间时,加倍数组的大小.正如您在下面的示例中所看到的,它并不是很困难:(为简洁起见,我省略了安全检查)

typedef struct {
  int *array;
  size_t used;
  size_t size;
} Array;

void initArray(Array *a, size_t initialSize) {
  a->array = (int *)malloc(initialSize * sizeof(int));
  a->used = 0;
  a->size = initialSize;
}

void insertArray(Array *a, int element) {
  // a->used is the number of used entries, because a->array[a->used++] updates a->used only *after* the array has been accessed.
  // Therefore a->used can go up to a->size 
  if (a->used == a->size) {
    a->size *= 2;
    a->array = (int *)realloc(a->array, a->size * sizeof(int));
  }
  a->array[a->used++] = element;
}

void freeArray(Array *a) {
  free(a->array);
  a->array = NULL;
  a->used = a->size = 0;
}
Run Code Online (Sandbox Code Playgroud)

使用它就是这么简单:

Array a;
int i;

initArray(&a, 5);  // initially 5 elements
for (i = 0; i < 100; i++)
  insertArray(&a, i);  // automatically resizes as necessary
printf("%d\n", a.array[9]);  // print 10th element
printf("%d\n", a.used);  // print number of elements
freeArray(&a);
Run Code Online (Sandbox Code Playgroud)

  • 永远不要忽略内存分配和重新分配的安全检查. (12认同)
  • %d和size_t ...在那里没有一点.如果您使用C99或更高版本,可以利用%z的添加 (5认同)
  • 我不认为你的大小会从零开始增长,因为它只是乘法(即 0*2 = 0)。添加常量有助于避免一开始就有大量零散的副本,并防止这种引导问题。我还建议通常使用小于 2 的乘数,以避免浪费大量空间的风险。n = (n*3)/2+8 是我最喜欢的。但说真的,如果你想用 C 语言做某事,你需要不要害怕像这种快速或改变语言的情况。使用指针与现代语言不同,并且可能会令人困惑,但它并不复杂。我相信你一定能得到它。 (3认同)
  • 这是性能权衡.如果你每次加倍,那么你有时会有100%的开销,平均为50%.3/2给你50%的最差和25%的典型.它也接近极限(phi)中Fibion​​acci序列的有效基数,这通常被称赞并用于其"指数但比基础-2更不强烈"的特征,但更容易计算.+8意味着相当小的数组最终不会做太多副本.它增加了一个乘法项,允许数组在其大小无关紧要的情况下快速增长.在专业用途中,这应该是可调的. (3认同)
  • 非常感谢代码.删除最后一个元素的`removeArray`方法也很整洁.如果您允许,我会将其添加到您的代码示例中. (2认同)
  • `a-&gt;array = (int *)realloc(a-&gt;array, a-&gt;size * sizeof(int));` 会在调用失败时创建一个悬空指针和泄漏。 (2认同)

Wol*_*lph 10

我能想到几个选项.

  1. 链接列表.您可以使用链接列表来制作动态增长的数组.但你不会是能够做到array[100],而不必通过走1-99第一.你可能也不方便使用它们.
  2. 大阵列.只需创建一个具有足够空间的数组即可
  3. 调整数组大小.知道大小后重新创建数组和/或每次空间用尽时创建一个新数组,并将所有数据复制到新数组.
  4. 链接列表数组组合.只需使用具有固定大小的数组,一旦空间不足,创建一个新数组并链接到该数组(跟踪数组和结构中下一个数组的链接是明智的).

在你的情况下很难说哪种选择最好.简单地创建一个大型阵列是最简单的解决方案之一,除非它真的很大,否则不应该给你带来太多问题.

  • 这里#1和#4都需要使用指针和动态内存分配.我建议在#3中使用`realloc` - 将数组分配给正常大小,然后在你用完时再增长它.如果需要,`realloc`将处理复制数据.至于OP关于内存管理的问题,你只需要在开始时"malloc"一次,在结束时"free"一次,并在每次用完房间时"realloc".这不是太糟糕. (3认同)

aut*_*tic 8

就像最初看起来比以后更加可怕的一切一样,克服最初恐惧的最好方法就是让自己沉浸在未知的不适之中!毕竟,有时我们学到的最多.

不幸的是,存在局限性.当你还在学习使用某个功能时,你不应该担任教师的角色.我经常阅读那些看似不知道如何使用的答案realloc(即目前接受的答案!)告诉别人如何错误地使用它,偶尔以他们省略错误处理为幌子,即使这是一个常见的陷阱需要提一下.这是一个解释如何realloc正确使用的答案.请注意,答案是将返回值存储到另一个变量中以执行错误检查.

每次调用函数时,每次使用数组时,都使用指针.转换是隐含的,如果有什么事情应该更加可怕,那就是我们看不到的事情往往会导致最多的问题.例如,内存泄漏......

数组运算符是指针运算符.array[x]真的是一个捷径*(array + x),可以分解为:*(array + x).这很可能*会让你感到困惑.我们可以进一步消除假设从问题除了x0,因此,array[0]变成*array因为添加0不会改变的价值...

......因此我们可以看到*array相当于array[0].您可以使用其中一个使用另一个,反之亦然.数组运算符是指针运算符.

malloc,realloc和朋友不要编造你已经使用了所有沿指针的概念; 他们只是它来实现一些其他功能,这是一种不同形式的存储持续时间,最适合您需要大小的动态变化.

令人遗憾的是,目前接受的答案StackOverflow上其他一些非常有根据的建议相悖,同时,错过了引入一个鲜为人知的功能的机会,该功能正是这个用例的灵感:灵活的数组会员!这实际上是一个非常破碎的答案...... :(

定义,在结构的末尾struct声明数组,没有任何上限.例如:

struct int_list {
    size_t size;
    int value[];
};
Run Code Online (Sandbox Code Playgroud)

这将允许您将您的数组与您的数组int相同count,并让它们像这样绑定可以非常方便!

sizeof (struct int_list)将表现为value大小为0,因此它将告诉您带有空列表的结构的大小.您仍需要添加传递给realloc的大小以指定列表的大小.

另一个方便的提示是记住它realloc(NULL, x)相当于malloc(x),我们可以使用它来简化我们的代码.例如:

int push_back(struct int_list **fubar, int value) {
    size_t x = *fubar ? fubar[0]->size : 0
         , y = x + 1;

    if ((x & y) == 0) {
        void *temp = realloc(*fubar, sizeof **fubar
                                   + (x + y) * sizeof fubar[0]->value[0]);
        if (!temp) { return 1; }
        *fubar = temp; // or, if you like, `fubar[0] = temp;`
    }

    fubar[0]->value[x] = value;
    fubar[0]->size = y;
    return 0;
}

struct int_list *array = NULL;
Run Code Online (Sandbox Code Playgroud)

我选择使用struct int_list **第一个参数的原因似乎并不是很明显,但是如果你考虑第二个参数,那么value从内部进行的任何更改push_back都不会对我们调用的函数可见,对吧?对于第一个参数也是如此,我们需要能够修改我们的array,不仅仅是在这里,也可能在我们传递给它的任何其他函数中 ...

array开始指着什么都没有; 这是一个空列表.初始化它与添加它相同.例如:

struct int_list *array = NULL;
if (!push_back(&array, 42)) {
    // success!
}
Run Code Online (Sandbox Code Playgroud)

PS 请记住,free(array);当你完成它!

  • 唉,@C-Star-Puppy,您的资源似乎根本没有提到的一个参考文献是 C 标准。这是您的编译器必须遵守的规范,并合法地称自己为 C 编译器。你的资源似乎与我的信息完全不矛盾。尽管如此,该标准实际上有一些示例,例如 [this gem](https://port70.net/~nsz/c/c11/n1570.html#6.5.2.1),其中显示 `array[index]` 实际上是变相的`ptr[index]`... *"下标运算符`[]`的定义是`E1[E2]`等同于`(*((E1)+(E2)))`"*你无法反驳标准 (2认同)

小智 6

基于Matteo Furlans 的设计,当他说“大多数动态数组实现都是从一些(小)默认大小的数组开始工作的,然后每当添加新元素时空间不足时就将数组的大小加倍”。下面“进行中的工作”的不同之处在于它的大小不会翻倍,它旨在仅使用所需的内容。为简单起见,我还省略了安全检查……也是基于brimboriums 的想法,我尝试在代码中添加删除功能……

storage.h 文件看起来像这样......

#ifndef STORAGE_H
#define STORAGE_H

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct 
    {
        int *array;
        size_t size;
    } Array;

    void Array_Init(Array *array);
    void Array_Add(Array *array, int item);
    void Array_Delete(Array *array, int index);
    void Array_Free(Array *array);

#ifdef __cplusplus
}
#endif

#endif /* STORAGE_H */
Run Code Online (Sandbox Code Playgroud)

storage.c 文件看起来像这样......

#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

/* Initialise an empty array */
void Array_Init(Array *array) 
{
    int *int_pointer;

    int_pointer = (int *)malloc(sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to allocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
        array->size = 0;
    }
}

/* Dynamically add to end of an array */
void Array_Add(Array *array, int item) 
{
    int *int_pointer;

    array->size += 1;

    int_pointer = (int *)realloc(array->array, array->size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer;
        array->array[array->size-1] = item;
    }
}

/* Delete from a dynamic array */
void Array_Delete(Array *array, int index) 
{
    int i;
    Array temp;
    int *int_pointer;

    Array_Init(&temp);

    for(i=index; i<array->size; i++)
    {
        array->array[i] = array->array[i + 1];
    }

    array->size -= 1;

    for (i = 0; i < array->size; i++)
    {
        Array_Add(&temp, array->array[i]);
    }

    int_pointer = (int *)realloc(temp.array, temp.size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
    } 
}

/* Free an array */
void Array_Free(Array *array) 
{
  free(array->array);
  array->array = NULL;
  array->size = 0;  
}
Run Code Online (Sandbox Code Playgroud)

main.c 看起来像这样......

#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

int main(int argc, char** argv) 
{
    Array pointers;
    int i;

    Array_Init(&pointers);

    for (i = 0; i < 60; i++)
    {
        Array_Add(&pointers, i);        
    }

    Array_Delete(&pointers, 3);

    Array_Delete(&pointers, 6);

    Array_Delete(&pointers, 30);

    for (i = 0; i < pointers.size; i++)
    {        
        printf("Value: %d Size:%d \n", pointers.array[i], pointers.size);
    }

    Array_Free(&pointers);

    return (EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)

期待后续的建设性批评...

  • 不是试图撕裂任何人,只是提供一些建设性的批评,即使没有你轻松地接近,这些批评也可能即将到来;) (2认同)
  • David,我一直在考虑您的评论“调整大小时将内存加倍比一次添加 1 个空间更有效:对 realloc() 的调用更少”。请您为我详细说明一下,为什么分配双倍的内存量并且可能不使用它(从而浪费内存)比只分配任务所需的内存量更好?我明白您所说的有关调用 realloc() 的意思,但为什么每次出现问题时都要调用 realloc() ?这不就是重新分配内存的目的吗? (2认同)

归档时间:

查看次数:

196548 次

最近记录:

7 年,2 月 前