【问题标题】:Dynamic Array in C - reallocC中的动态数组 - realloc
【发布时间】:2011-09-27 01:38:39
【问题描述】:

我知道如何构建动态分配的数组,但不知道如何扩展它们。

例如我有如下界面..

void insertVertex( vertex p1, vertex out[], int *size);

此方法获取一个顶点并将其存储到 out 数组中。存储顶点后,我会增加未来调用的长度计数。

p1 - 是我要添加的顶点。

out[] - 是我需要存储的数组(总是满的)

length - 当前长度

顶点定义为..

typedef struct Vertex{
int x;
int y;
} Vertex;

这是我在 Java 中使用的..

Vertex tempOut = new Vertex[size +1];
//Code to deep copy each object over
tempOut[size] = p1;
out = tempOut;

这是我相信我可以在 c.. 中使用的东西。

out = realloc(out, (*size + 1) * sizeof(Vertex));
out[(*size)] = p1;

但是,我不断收到一条错误消息,指出对象不是动态分配的。

我找到了一个可以解决这个问题的解决方案。我没有使用 Vertex*,而是切换到 Vertex** 并存储指针与顶点。但是,在切换所有内容之后,我发现我忽略了这样一个事实,即单元测试将为我提供一个必须存储所有内容的 Vertex out[]。

我也尝试了以下方法,但没有成功。

Vertex* temp = (Vertex *)malloc((*size + 1) * sizeof(Vertex));
for(int i = 0; i < (*size); i++)
{
temp[i] = out[i];
}
out = temp;

但是,无论我在这两个之后测试时做什么,返回的数组都没有改变。

更新 - 要求的信息

out - 定义为一个顶点数组(Vertex out[])

它最初是用我的多边形中的顶点数构建的。例如。

out = (Vertex *)malloc(vertexInPolygon * sizeof(Vertex))

其中 vertexInPolygon 是多边形中顶点数的整数。

长度是一个错字,应该是大小。

大小是一个整数指针

int *size = 0;

每当一个顶点在裁剪平面中时,我们将它添加到顶点数组中,并将大小增加一。

更新

为了更好地解释我自己,我想出了一个简短的程序来展示我正在尝试做的事情。

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

typedef struct Vertex {
    int x, y;
} Vertex;

void addPointerToArray(Vertex v1, Vertex out[], int *size);

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;
    
    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;
    
    //  Update Size
    *size = newSize;
}

int main (int argc, const char * argv[])
{
    //  This would normally be provided by the polygon
    int *size = malloc(sizeof(int)); *size = 3;
    
    //  Build and add initial vertex
    Vertex *out = (Vertex *)malloc((*size) * sizeof(Vertex));
    Vertex v1; v1.x = 1; v1.y =1;
    Vertex v2; v2.x = 2; v2.y =2;
    Vertex v3; v3.x = 3; v3.y =3;
    
    out[0] = v1;
    out[1] = v2;
    out[2] = v3;
    
    //  Add vertex
    //  This should add the vertex to the last position of out
    //  Should also increase the size by 1;
    Vertex vertexToAdd; vertexToAdd.x = 9; vertexToAdd.y = 9;
    addPointerToArray(vertexToAdd, out, size);
    
    for(int i =0; i < (*size); i++)
    {
        printf("Vertx: (%i, %i) Location: %i\n", out[i].x, out[i].y, i);
    }
   
}

【问题讨论】:

  • out 定义为什么? lengthsize 是什么?
  • 你说realloc抱怨它不是动态分配的。所以……是吗?您必须先使用malloc,才能稍后使用realloc
  • 你能否在lengthsize传递给insertVertex()之前显示它的定义
  • evgeny out 定义为... Vertex *out = (Vertex *)malloc(sizeof(Vertex) * numOfVerInPolygon) 其中 numOfVerInPolygon 是多边形中的顶点数。 Daniel Brockman 是的,它是用 malloc 定义的。在通过之前,它被定义如下。 Vertex *out = (Vertex *)malloc(sizeof(Vertex) * numOfVerInPolygon) TimothyJones 抱歉,长度和大小是一回事。我更新了帖子,以便所有内容都使用大小。 size 从零开始: int *size = 0;当我们遍历每个顶点时,如果它在剪切平面中,则在添加到 out[](Vertex) 后大小会增加一
  • int *size = 0 将使用 NULL 初始化大小,并将其指向任何内容 - 不会给您指向设置为 0 的 int 的指针。除非您将其指向实际的 int稍后,这可能是您的问题。

标签: c


【解决方案1】:

一个长期的问题是您没有从addPointerToArray() 函数返回更新后的数组指针:

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

当你重新分配空间时,它可以移动到一个新的位置,所以realloc()的返回值不需要和输入指针一样。这可能会在您添加到数组时没有其他内存分配进行时起作用,因为realloc() 将在有空间的情况下扩展现有分配,但是一旦您在读取顶点时开始分配其他数据,它将严重失败.有几种方法可以解决此问题:

Vertex *addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
    return out;
}

和调用:

out = addPointerToArray(vertexToAdd, out, size);

或者,你可以传入一个指向数组的指针:

void addPointerToArray(Vertex v1, Vertex **out, int *size)
{
    int newSize = *size;
    newSize++;

    *out = realloc(*out, newSize * sizeof(Vertex));
    (*out)[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

和调用:

out = addPointerToArray(vertexToAdd, &out, size);

这些重写都没有解决微妙的内存泄漏问题。问题是,如果你用返回值覆盖你传递给realloc() 的值但realloc() 失败,你会丢失指向(仍然)分配的数组的指针 - 泄漏内存。当您使用realloc() 时,请使用如下成语:

Vertex *new_space = realloc(out, newSize * sizeof(Vertex));
if (new_space != 0)
    out = new_space;
else
    ...deal with error...but out has not been destroyed!...

请注意,使用realloc() 一次添加一个新项目会导致(可能导致)二次行为。您最好分配一大块内存 - 例如,将分配的空间加倍:

int newSize = *size * 2;

如果您担心过度分配,在读取循环结束时,您可以使用realloc() 将分配的空间缩小到数组的确切大小。但是,还有更多的簿记工作要做;您需要的值:分配给数组的顶点数,以及实际使用的顶点数。

最后,至少现在,请注意,您真的应该保持绝对一致并使用addPointerToArray() 将前三个条目添加到数组中。我可能会使用类似于这个(未经测试的)代码的东西:

struct VertexList
{
    size_t    num_alloc;
    size_t    num_inuse;
    Vertex   *list;
};

void initVertexList(VertexList *array)
{
    // C99: *array = (VertexList){ 0, 0, 0 };
    // Verbose C99: *array = (VertexList){ .num_inuse = 0, .num_alloc = 0, .list = 0 };
    array->num_inuse = 0;
    array->num_alloc = 0;
    array->list      = 0;
}

void addPointerToArray(Vertex v1, VertexList *array)
{
    if (array->num_inuse >= array->num_alloc)
    {
        assert(array->num_inuse == array->num_alloc);
        size_t new_size = (array->num_alloc + 2) * 2;
        Vertex *new_list = realloc(array->list, new_size * sizeof(Vertex));
        if (new_list == 0)
            ...deal with out of memory condition...
        array->num_alloc = new_size;
        array->list      = new_list;
    }
    array->list[array->num_inuse++] = v1;
}

这使用了realloc() 的反直觉属性,如果传入的指针为空,它将执行malloc()。您可以改为检查array-&gt;list == 0,然后使用malloc(),否则使用realloc()

您可能会注意到这种结构也简化了调用代码;您不再需要处理主程序中单独的int *size;(及其内存分配);大小有效地捆绑到VertexList 结构中,为num_inuse。主程序现在可能会启动:

int main(void)
{
    VertexList array;
    initVertexList(&array);
    addPointerToArray((Vertex){ 1, 1 }, &array);  // C99 compound literal
    addPointerToArray((Vertex){ 2, 2 }, &array);
    addPointerToArray((Vertex){ 3, 3 }, &array);
    addPointerToArray((Vertex){ 9, 9 }, &array);

    for (int i = 0; i < array->num_inuse; i++)
        printf("Vertex %d: (%d, %d)\n", i, array->list[i].x, array->list[i].y, i);

    return 0;
}

(巧合的是,这个序列只会调用一次内存分配,因为新大小(old_size + 2) * 2第一次为数组分配了4个元素。通过添加新点或通过细化来轻松执行重新分配(old_size + 1) * 2 的公式,或者...

如果您打算从内存分配失败中恢复(而不是在发生时退出),那么您应该修改 addPointerToArray() 以返回状态(成功,不成功)。

另外,函数名可能应该是addPointToArray()addVertexToArray() 甚至addVertexToList()

【讨论】:

  • Leffier 谢谢。你和桑托伊都帮了我大忙。我已经更新了我的方法,想知道这是否可以。 'void addPointerToPointerArray(Vertex v1, Vertex **out, int *size) { int newSize = *size;新尺寸++;顶点 *tempOut = (顶点 *)malloc(sizeof(Vertex) * newSize); if (tempOut != 0) { int i = 0; while (i
  • 是的 - 有一些警告。 while 循环应该是for 循环,或者替换为memmove()memcpy()。使用realloc() 意味着您不会自己进行复制,也可能不需要复制所有内容。当您更多地使用该功能时,每次将大小增加一成为一个问题,尤其是在这种“始终复制”模式下。第一次加一个,复制3个;接下来,你复制4;接下来,您复制 5,等等。但是您所拥有的看起来应该可以完成这项工作。 (我重新格式化它以观察它;我没有通过编译器运行它,更不用说valgrind。)
  • 很好的解释。谢谢。
【解决方案2】:

我有一些建议供您参考:
1. 在使用realloc时不要使用相同的输入和输出参数,因为它可以返回NULL,以防内存分配失败并且先前指向的内存被泄漏。 realloc 可能会返回新的内存块(感谢@Jonathan Leffler 指出,我错过了这一点)。您可以将代码更改为以下几行:

Vertex * new_out = realloc(out,  newSize * sizeof(Vertex));
if( NULL != new_out )
{    
    out = new_out;
    out[(*size)] = v1;
}
else
{
 //Error handling & freeing memory
}

2。添加NULL 检查malloc 调用并在内存出现故障时处理错误。
3. 对free 的呼叫丢失。
4、将addPointerToArray()的返回类型由void改为bool,表示添加是否成功。如果realloc 失败,您可以返回失败说,false 否则你可以返回成功说,true。
@MatthewD 已经指出了与过度复制等有关的其他观察结果。
@Jonathan Leffler 很少有好的观察结果(: 希望这会有所帮助!

【讨论】:

  • +0.5:你提出了一些好的观点——特别是关于滥用out = realloc(out, newSize * sizeof(Vertex));。但是,您没有提到realloc() 可能返回的指针与提供的指针不同的关键点——数据可能会移动。这一事实需要不同的策略;看我的回答。
  • @Jonathan Leffler:非常感谢!我错过了......回复已编辑。
【解决方案3】:

您的示例程序对我来说很好用。我在 Linux 上使用 gcc 4.1.1。

但是,如果您的实际程序与您的示例程序类似,则效率相当低!

例如,您的程序大量复制内存:结构复制 - 初始化out,将顶点传递给addPointerToArray(),内存复制通过realloc()

通过指针而不是副本传递结构。

如果您需要大量增加列表类型的大小,最好使用链表、树或其他结构(取决于您以后需要哪种访问方式)。

如果您只需要一个向量类型,实现动态大小向量的标准方法是分配一块内存(例如,16 个顶点的空间),并在每次空间不足时将其大小加倍。这将限制所需重新分配的数量。

【讨论】:

    【解决方案4】:

    尝试这些更改,它应该可以工作。

    void addPointerToArray(Vertex v1, Vertex (*out)[], int *size)
    {
        int newSize = *size;
        newSize++;
    
        *out = realloc(out, newSize * sizeof(Vertex));
        *out[(*size)] = v1;
    
        //  Update Size
        *size = newSize;
    }
    

    然后像这样调用函数

    addPointerToArray(vertexToAdd, &out, size);
    
    • 有一种解决此类问题的简单方法(您可能已经知道这一点)。当您将参数传递给函数时,请考虑堆栈中到底发生了什么,然后结合这样一个事实,即您对堆栈上存在的变量所做的任何更改都会在函数出现时消失。这种想法应该可以解决与传递参数相关的大部分问题。

    • 进入优化部分,选择正确的数据结构对于任何项目的成功都至关重要。就像上面有人指出的那样,链表是比数组更好的数据结构。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-20
      • 2014-06-03
      • 2016-11-22
      • 2021-04-07
      • 2016-08-12
      • 2017-06-08
      相关资源
      最近更新 更多