C动态增长数组


126

我有一个程序可以读取游戏中实体的“原始”列表,并且我打算制作一个数组,其中包含不确定数量的实体的索引号(int),用于处理各种事物。我想避免使用过多的内存或CPU来保持此类索引...

到目前为止,我使用的一种快速而又肮脏的解决方案是在主处理函数(局部焦点)中声明具有最大游戏实体大小的数组,以及另一个用于跟踪已添加到列表中的整数的整数。这并不令人满意,因为每个列表都包含3000多个数组,虽然数量不算多,但是却感觉很浪费,因为我可能会使用6-7个列表的解决方案来实现各种功能。

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

数组不会离开局部函数范围(它们将被传递给函数,然后被丢弃),以防万一。

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


1
这是C语言中的一个(非常非常小的)问题,但是您如何错过所有的C ++和C#解决方案呢?
伊格纳西奥·巴斯克斯

11
“如果指针是唯一的解决方案,那么我该如何跟踪它们以避免泄漏?” 关心,关注和重视。这就是为什么人们首先担心C的原因。
克里斯·卢茨

27
如果不使用指针,就无法有效地使用C。不要害怕
qrdl 2010年

没有大库的所有也为结构如只有一个功能:stackoverflow.com/questions/3456446/...
user411313

6
在没有指针的情况下使用C就像在没有燃料的情况下使用汽车。
martinkunev

Answers:


210

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

如果需要动态数组,则无法转义指针。你为什么害怕呢?他们不会咬人(只要您注意,就是这样)。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 = 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 = 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;
}

使用它非常简单:

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

1
感谢您提供示例代码。我使用一个大数组实现了特定的功能,但是将使用此功能来实现其他类似的功能,在我将其控制后,将其他功能改回:)
Balkania 2010年

2
非常感谢您提供的代码。一个removeArray是摆脱了最后一个元素的方法也将是整齐。如果允许的话,我会将其添加到您的代码示例中。
brimborium

5
%d和size_t ...有点不在那里。如果您使用C99或更高版本,可以利用添加%z的好处
Randy Howard

13
切勿在内存分配和重新分配时忽略安全检查。
Alex Reynolds

3
这是性能的折衷。如果您每次加倍,那么有时您的开销为100%,平均为50%。3/2使您最差50%,典型值25%。它也接近极限值(phi)中的Fibion​​acci序列的有效底数,该底数因其“指数但比底数2强”而广受赞誉和使用,但更易于计算。+8表示相对较小的阵列不会产生太多副本。它增加了一个乘法项,如果数组的大小无关紧要,则允许数组快速增长。在专业用途中,这应该是可调的。
Dan Sheppard'7

10

我可以想到几种选择。

  1. 链表。您可以使用链接列表来制作动态增长的类似数组的东西。但是,如果array[100]没有先走一步,您将无能为力1-99。而且,使用它们可能都不那么方便。
  2. 大阵列。只需创建一个具有足够空间的阵列即可容纳所有内容
  3. 调整数组大小。知道大小后重新创建阵列,并且/或者每当您用完一些空白空间就创建一个新阵列,然后将所有数据复制到新阵列中。
  4. 链接列表数组组合。只需使用固定大小的数组,一旦空间用完,就创建一个新数组并链接到该数组(最好跟踪该数组以及指向结构中下一个数组的链接)。

很难说哪种选择最适合您的情况。简单地创建一个大型数组当然是最简单的解决方案之一,除非它真的很大,否则不会给您带来很多问题。


对于现代的2D游戏,七个3264整数数组的声音如何?如果我只是偏执,解决方案将是大型阵列。
巴尔卡尼亚

3
无论如何,#1和#4都需要使用指针和动态内存分配。我建议realloc与#3一起使用-为数组分配正常大小,然后在用完时将其增大。 realloc将在必要时处理复制您的数据。至于OP的内存管理问题,您只需要malloc在开始时free一次,在结束时一次以及realloc每次用尽空间即可。还算不错
Borealid

1
@Balkania:7个3264整数数组是100KB以下的头发。这根本不是太多的记忆。
Borealid 2010年

1
@Balkania:7 * 3264 * 32 bit听起来像91.39 kilobytes。这些天来,以任何标准来衡量,都没有那么多;)
Wolph

1
这种特殊的遗漏是一种耻辱,因为它并不完全明显时,应发生什么realloc回报NULLa->array = (int *)realloc(a->array, a->size * sizeof(int));...也许它会一直最好的,因为写的:int *temp = realloc(a->array, a->size * sizeof *a->array); a->array = temp;......这样,这将是显而易见的,无论确实发生需要发生之前NULL值将分配给a->array(如果有的话)。
自闭症2015年

10

与起初看起来比后来看起来更可怕的一切一样,克服最初的恐惧的最佳方法是让自己陷入未知世界的不适中!毕竟,有时候是我们学习最多的时候。

不幸的是,有局限性。例如,当您仍在学习使用功能时,就不应扮演老师的角色。我经常从那些看似不知道如何使用realloc(即当前接受的答案!)的人那里读到答案,告诉其他人如何不正确地使用它,有时冒充他们忽略了错误处理,尽管这是一个常见的陷阱。需要提到的。这是解释如何realloc正确使用的答案请注意,答案是将返回值存储到另一个变量中,以便执行错误检查。

每次调用函数和每次使用数组时,都在使用指针。转换是隐式发生的,如果有的话甚至更可怕,因为我们看不到的东西通常会引起最多的问题。例如,内存泄漏...

数组运算符是指针运算符。array[x]确实是的快捷方式*(array + x),可以分为:*(array + x)。这很可能会使*您感到困惑。我们可以通过假设x为进一步消除问题中的加法0,因此array[0]成为*array因为加法0不会改变值...

...因此我们可以看到*array等同于array[0]。您可以在要使用另一个的地方使用它,反之亦然。数组运算符是指针运算符。

mallocrealloc并且朋友不会发明您一直使用的指针的概念;他们只是使用它来实现其他一些功能,这是存储时间的另一种形式,最适合您希望大小动态变化的情况

令人遗憾的是,当前接受的答案StackOverflow上其他一些非常有根据的建议背道而驰,与此同时,错过了引入一个鲜为人知的功能的机会,该功能正是这种用例的亮点:灵活数组成员!这实际上是一个非常糟糕的答案... :(

定义时struct,请结构末尾声明数组,没有任何上限。例如:

struct int_list {
    size_t size;
    int value[];
};

这将使您可以将数组的数组与您的数组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;

我选择struct int_list **用作第一个参数的原因似乎并不立即显而易见,但是如果您考虑第二个参数,value则从内部进行的任何更改对于push_back我们从中调用的函数都是不可见的,对吗?这同样适用于第一个参数,我们需要能够修改我们的array,不只是在这里,但也可能在其它任何功能/ s,我们把它传递给 ...

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

struct int_list *array = NULL;
if (!push_back(&array, 42)) {
    // success!
}

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


array[x]的确是*(array + x)[,] 的快捷方式”您确定吗?请参阅有关其不同行为的说明:eli.thegreenplace.net/2009/10/21/…
C-Star-W-Star

1
@,@ C-Star-Puppy,您的资源似乎根本没有提及的一个参考是C标准。那就是您的编译器必须遵循的规范,以合法地称自己为C编译器。您的资源似乎与我的信息完全没有矛盾。尽管如此,该标准实际上有一些例子,如这种宝石在那里的发现,array[index]实际上是ptr[index]变相... “下标运算符的定义[]是,E1[E2]等同于(*((E1)+(E2)))你不能反驳STD
自闭症

试试这个演示,@ C-星小狗:int main(void) { unsigned char lower[] = "abcdefghijklmnopqrstuvwxyz"; for (size_t x = 0; x < sizeof lower - 1; x++) { putchar(x[lower]); } }......你可能需要#include <stdio.h><stddef.h>...你看我怎么写x[lower](有x是整数类型),而不是lower[x]?C编译器不在乎,因为C编译器*(lower + x)的值与相同*(x + lower),并且lower[x]前者在- x[lower]后者中。所有这些表达式都是等效的。试出来......看到自己,如果你不能把我的话...
自闭症

……然后当然是这部分,我将重点放在其中,但是您确实应该在不加强调的情况下阅读整个引文:“除非它是sizeof运算符,_Alignof运算符或一元&运算符,或者是用于初始化数组的字符串文字,具有“数组类型”类型的表达式将转换为具有“指向该类型的指针”类型的表达式,该表达式指向数组的初始元素对象,不是左值。如果数组对象具有寄存器存储类,则行为未定义。” 顺便说一句,函数也是如此。
自闭症

哦,最后一点要说的是@ C-Star-Puppy,Microsoft C ++不是C编译器,而且已经有近20年了。您可以启用suuuure C89模式,但我们在1980年代末开始发展。有关该主题的更多信息,建议阅读本文 ...然后切换到实际的C编译器,例如gccclang所有C编译器,因为您会发现有很多采用C99功能的软件包...
自闭症

3

建立在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 */

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

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);
}

期待随之而来的建设性批评


1
如果您寻求建设性的批评,最好将其张贴在Code Review上。也就是说,有两个建议:malloc()在尝试使用分配之前,代码必须检查对调用的成功。同样,将结果直接分配给realloc()要重新分配的原始内存的指针是错误的;如果realloc()失败,将NULL被返回,并且代码会留下内存泄漏。调整大小时将内存加倍比每次添加1个空间效率更高:调用更少realloc()
ex nihilo '18

1
我知道我会被撕裂,当我说“建设性批评”时,我只是在开玩笑...谢谢您的建议...

2
不试图撕扯任何人,只是提出一些建设性的批评,即使没有您轻松的接近也可能会出现;)
ex nihilo 18/12/9

1
大卫,我一直在考虑您的评论“调整大小时将内存加倍比每次添加1个空间要有效得多:对realloc()的调用更少”。您能为我详细说明一下,为什么分配仅两倍于任务所需数量的内存比分配双倍的内存量(可能不使用它,从而浪费内存)更好?我明白了您对调用realloc()的看法,但是为什么每次出现问题时都调用realloc()?那不是重新分配内存的用吗?

1
虽然严格加倍可能不是最佳选择,但它肯定比一次增加一个字节(或一个字节int等)更好。加倍是典型的解决方案,但我认为没有适合所有情况的最佳解决方案。这就是为什么加倍是一个好主意的原因(也可以使用1.5之类的其他其他因素):如果您从合理的分配开始,则可能根本不需要重新分配。当需要更多内存时,合理的分配将增加一倍,依此类推。这样,您可能只需要打一个或两个电话即可realloc()
前nihilo

2

当你说

创建一个数组,其中包含不确定数目的实体的索引号(int)

您基本上是说您正在使用“指针”,但是它是一个数组范围的本地指针而不是内存范围的指针。由于您在概念上已经在使用“指针”(即,引用数组中的元素的ID号),为什么不使用常规指针(即,引用最大数组中的元素的ID号:整个内存) )。

您可以使它们存储指针来代替存储资源ID号的对象。基本上是同一件事,但是效率更高,因为我们避免将“数组+索引”转换为“指针”。

如果您将指针视为整个内存的数组索引(实际上就是指针),它并不令人恐惧。


2

要创建任何类型的无限制项目的数组,请执行以下操作:

typedef struct STRUCT_SS_VECTOR {
    size_t size;
    void** items;
} ss_vector;


ss_vector* ss_init_vector(size_t item_size) {
    ss_vector* vector;
    vector = malloc(sizeof(ss_vector));
    vector->size = 0;
    vector->items = calloc(0, item_size);

    return vector;
}

void ss_vector_append(ss_vector* vec, void* item) {
    vec->size++;
    vec->items = realloc(vec->items, vec->size * sizeof(item));
    vec->items[vec->size - 1] = item;
};

void ss_vector_free(ss_vector* vec) {
    for (int i = 0; i < vec->size; i++)
        free(vec->items[i]);

    free(vec->items);
    free(vec);
}

以及如何使用它:

// defining some sort of struct, can be anything really
typedef struct APPLE_STRUCT {
    int id;
} apple;

apple* init_apple(int id) {
    apple* a;
    a = malloc(sizeof(apple));
    a-> id = id;
    return a;
};


int main(int argc, char* argv[]) {
    ss_vector* vector = ss_init_vector(sizeof(apple));

    // inserting some items
    for (int i = 0; i < 10; i++)
        ss_vector_append(vector, init_apple(i));


    // dont forget to free it
    ss_vector_free(vector);

    return 0;
}

这个向量/数组可以容纳任何类型的项目,并且大小完全是动态的。


0

好吧,我想如果您需要删除一个元素,您将制作一个数组副本,以鄙视要排除的元素。

// inserting some items
void* element_2_remove = getElement2BRemove();

for (int i = 0; i < vector->size; i++){
       if(vector[i]!=element_2_remove) copy2TempVector(vector[i]);
       }

free(vector->items);
free(vector);
fillFromTempVector(vector);
//

假设getElement2BRemove()copy2TempVector( void* ...)fillFromTempVector(...)是辅助方法来处理的临时载体。


目前尚不清楚这实际上是对提出的问题的答案,还是评论。

如果有人有更好的主意,这是“如何做”的观点,我要确认(我错了吗?)。;)
JOSMAR BARBOSA-M4NOV3Y

我想我听不懂你的最后一句话。由于SO不是一个线程论坛,因此答案中的此类开放问题看起来很奇怪。

1
我把你的最后一句话固定在我想你想说的地方。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.