【问题标题】:C: using realloc for high performance with an array of structsC:通过结构数组使用 realloc 以获得高性能
【发布时间】:2017-12-26 17:04:08
【问题描述】:

我正在使用 realloc 来调整包含 3 个点 x、y 和 z 的结构数组的大小。这个结构被封装在另一个结构中,该结构包含数组、数组的长度和一个“保留”值,当很明显更多的点结构将附加到结构数组。
我正在使用如下所示的 Makefile 进行编译:

CFLAGS = -g -Wall
LIBS = -lm

default: echo "You must specify a target, e.g. file1, file2" 


file2:
    gcc $(CFLAGS) -o $@ test.c file2.c $(LIBS)

我有一个函数来初始化一个空数组结构,一个将数组重置为空并释放任何动态分配的内存,一个将一个点附加到数组的末尾,一个删除一个由索引指定的点价值。

我遇到了两个我找不到原因的错误。一个是我的代码返回一个非零状态码 1,另一个是当我附加几千个点时长度似乎减一。
我让 append 函数完成所有工作,但如果我应该在初始化时分配动态内存,请告诉我。我很确定我的重置和删除功能正在按预期工作。请看一下追加。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <assert.h>

typedef struct point
{
  int x, y, z;
} point_t;

typedef struct 
{
  // number of points in the array
  size_t len;

  // pointer to an array of point_t structs

  point_t* points;


  size_t reserved; 

} point_array_t;


void point_array_initial( point_array_t* pa )
{
    assert(pa);
    pa->len = 0;
    pa->reserved = 0;
    pa->points=NULL;
}   


void point_array_reset( point_array_t* pa )
{//just free the array and set pa to NULL

    assert(pa);

    pa->points = memset(pa->points, 0, sizeof(point_t)*(pa->len));
    pa->len = 0;
    pa->reserved=0;
    free(pa->points);
    pa->points=NULL;
}


int point_array_append( point_array_t* pa, point_t* p )
{

    assert(pa);
    assert(p);
    if(pa == NULL)//something wrong with intialization or reset
    {
        return 1;
    }
    if(p == NULL)//nothing to append
    {
        return 1;
    }
    //append the first point 
    if(pa->len == 0)
    {
        pa->len=1;
        pa->reserved=pa->len*2;
        pa->points = malloc(sizeof(point_t)* (pa->reserved));
        if(pa->points == NULL)//malloc failed
        {
            return 1;
        }

        pa->points[pa->len-1].x = p->x;
        pa->points[pa->len-1].y = p->y;
        pa->points[pa->len-1].z = p->z;
    }

    if (pa->reserved > pa->len )
    {
        pa->len+=1;
        pa->points[pa->len-1].x = p->x;//insert at index 0
        pa->points[pa->len-1].y = p->y;
        pa->points[pa->len-1].z = p->z;

    }
    //when we run out of space in reserved (len has caught up)
    else if(pa->reserved == pa->len)
    {
        pa->len+=1;
        pa->reserved=pa->len*2;
        pa->points=realloc(pa->points, sizeof(point_t)*(pa->reserved));//doubling size of array
        pa->points[pa->len-1].x = p->x;//TODO: change formula to find insertion point
        pa->points[pa->len-1].y = p->y;
        pa->points[pa->len-1].z = p->z;
    }



    return 0;
}


int point_array_remove( point_array_t* pa, unsigned int i )
{

    assert(pa);
    if (i >= pa->len)//out of bounds
    {
        return 1;
    }   

    if(pa->len==0)//0 elements trying to remove from empty array
    {
        //pa->len=0;
        //free(pa->points);
        //pa->points=NULL; 
        return 1;
    }
    else if(pa->len ==1)//remove only element
    {
        pa->len-=1;//no copying required, just shorten
        pa->points=realloc(pa->points, sizeof(point_t)*(pa->len));
        //free(pa->points);
        //pa->points=NULL;
    }
    else//array size is longer than 1 or 0
    {
        pa->points[i].x = pa->points[pa->len-1].x;
        pa->points[i].y = pa->points[pa->len-1].y;
        pa->points[i].z = pa->points[pa->len-1].z;  
        pa->len-= 1;//shorten array size
        pa->reserved = pa->len*2;
        pa->points=realloc(pa->points, sizeof(point_t)*(pa->len));//could reallocate for reserve here to increase speed.
    }   

    return 0;
}

【问题讨论】:

  • 只需编码memset(pa-&gt;points, 0, sizeof(point_t)*(pa-&gt;len)); 而不分配memset 的(无用)结果
  • 据我所知,重置函数中的 memset 行毫无意义。摆脱它。而且数学肯定比你在代码的其他各个部分中做的更简单。
  • 调试器很吓人。 . . 所以用一些你知道很好的简单代码来感受一下,你可以引入故意的错误。永远不要试图同时解决两个问题。
  • pa-&gt;len-=1;//no copying required, just shorten pa-&gt;points=realloc(pa-&gt;points, sizeof(point_t)*(pa-&gt;len)); :需要更改pa-&gt;reserved
  • @CraigEstey,我不是 100% 清楚“失败”的含义,但在该案例代码的末尾添加了一个 return 0;

标签: c dynamic structure realloc


【解决方案1】:

append 函数中 if(pa-&gt;len == 0) 主体后缺少 else:第一个点被追加了两次。

请注意,您在此函数中有太多特殊情况。它可以简化为一个测试:如果数组太小,重新分配它,然后追加点。

其他简化也是可能的:

  • 测试if (pa-&gt;len == 0)//0 elements trying to remove from empty array与前一个是多余的。

  • 利用realloc(NULL, size) 等同于malloc(size)realloc(p, 0) 等同于free(p)free(NULL) 的事实。

  • 请注意realloc() 可能会失败,即使在缩小块时也是如此。

  • 您应该只在数组变得太稀疏时才缩小数组,而不是每次调用point_array_remove

这是一个更简单的版本:

#include <assert.h>
#include <stdlib.h>

typedef struct point {
    int x, y, z;
} point_t;

typedef struct {
    size_t len;      // number of valid points in the array
    size_t reserved; // allocated number of points in the array
    point_t *points; // pointer to an array of point_t structs
} point_array_t;

void point_array_initial(point_array_t *pa) {
    assert(pa);
    pa->len = 0;
    pa->reserved = 0;
    pa->points = NULL;
}

void point_array_reset(point_array_t *pa) {
    assert(pa);
    free(pa->points);
    pa->len = 0;
    pa->reserved = 0;
    pa->points = NULL;
}

int point_array_append(point_array_t *pa, const point_t *p) {
    point_t *points;

    assert(pa);
    assert(p);
    // no need to test pa nor p, asserts would already abort
    points = pa->points;
    if (pa->len >= pa->reserved || points == NULL) {
        // reallocate of points array is too small
        size_t newsize = pa->reserved;
        if (newsize < pa->len)
            newsize = pa->len;
        if (newsize < 1)
            newsize = 1;
        newsize += newsize;
        points = realloc(points, newsize * sizeof(*points);
        if (points == NULL)
            return 1;
        pa->points = points;
        pa->reserved = newsize;
    }
    // append point structure
    points[pa->len++] = *p;
    return 0;
}

int point_array_remove(point_array_t *pa, unsigned int i) {
    point_t *points;

    assert(pa);
    if (i >= pa->len || pa->points == NULL) { //out of bounds or invalid array
        return 1;
    }
    if (pa->len - i > 1) {
        memmove(&pa->points + i, &pa->points + i + 1,
                sizeof(*pa->points) * (pa->len - i - 1));
    }
    pa->len--;
    if (pa->reserved >= pa->len * 3) {
        size_t newsize = pa->len * 2;
        // shorten the array with care.
        // note that the array will be freed when it becomes empty
        // no special case needed.
        points = realloc(pa->points, sizeof(*points) * newsize);
        if (points != NULL) {
            pa->points = points;
            pa->reserved = newsize;
        }
    }
    return 0;
}

【讨论】:

  • 我想我知道如何修复它,但需要再次查看它为什么会被附加两次。所以所有其他代码都应该属于else
  • 是的,你测试if(pa-&gt;len == 0),然后添加一个点,然后在将1添加到len并设置reserved之后测试if (pa-&gt;reserved &gt; pa-&gt;len )
  • @DavidC.Rankin,是的,我现在后悔使用别人提供给我的测试代码。
  • @chqrlie,请给我一些时间来查看您的解决方案代码。我仍在尝试了解取消引用的窍门。
【解决方案2】:

除了 chqrlie 指出的错误之外,这里还有一些关于您的代码的其他想法。

对于非调试版本,CFLAGS 的更好选择是

-Wall -Wextra -O3

添加 -pedantic 以获得一些额外的警告,您可以将 -Ofast 与 gcc >= 4.6 一起使用。

永远不要 realloc 指针本身,如果 realloc 失败,则返回 NULL 并且您丢失了对原始内存块的引用 - 并造成内存泄漏,因为您不再拥有阻止free。在验证 realloc 成功之前,不要增加 lenreserved。相反,始终使用临时指针并仅在成功时递增值,例如

else if(pa->reserved == pa->len)
{
    void *tmp = realloc(pa->points, sizeof(point_t)*(pa->len + 1) * 2);
    if (!tmp) {
        /* handle error - exit or return */
    }
    pa->points = tmp;
    pa->len+=1;
    pa->reserved=pa->len*2;
}

如果您只是想将数组缩短一个,则以下问题看起来像:

else if(pa->len ==1)//remove only element
{
    pa->len-=1;//no copying required, just shorten
    pa->points=realloc(pa->points, sizeof(point_t)*(pa->len));
    //free(pa->points);
    //pa->points=NULL;
}
else//array size is longer than 1 or 0
{
    pa->points[i].x = pa->points[pa->len-1].x;
    pa->points[i].y = pa->points[pa->len-1].y;
    pa->points[i].z = pa->points[pa->len-1].z;  
    pa->len-= 1;//shorten array size
    pa->reserved = pa->len*2;
    pa->points=realloc(pa->points, sizeof(point_t)*(pa->len));//could reallocate for reserve here to increase speed.
}   

在上面的else 中,您将前一点分配给最后一点,然后切断最后一点——要么我不明白你想要完成什么,要么它没有按照你的想法做。在任何一种情况下,除非你有一些令人信服的理由想要 realloc 将数组缩短一个(我会等到所有添加/删除操作完成,然后在 len 元素上调用最终的 realloc 来精确调整你的内存使用)。相反,我会将上面的全部内容替换为:

else
    pa->len -= 1;

没有必要搞乱其他任何事情。您实际上忽略了最后一行中的数据 - 这不会造成任何伤害,直到您的下一次添加覆盖这些值。

【讨论】:

  • 我很抱歉之前没有指定要求,但是 remove 中的 int i 参数指定要删除的索引,并且要求是不稳定的删除,可能会改变数组的顺序。因此,我将最后一个索引元素复制到要删除的索引中,然后缩短长度。
  • 那是不清楚的。如果要删除中间的一个元素,则需要按顺序复制 i+1i 直到达到 len。不用道歉,只是有点混乱。我明白你现在在做什么——只需将要删除的点保存在要删除的点中。
  • 我一定会按照重新分配位置和内存泄漏的建议更改顺序。
  • @NickAnoyot 考虑到这些要求(我认为更清楚),然后正如我在一般评论中提到的,数学可以大大简化。 see it live。至少,这似乎就是您想要做的所有事情,但我提醒您,如果您发现自己坐在或靠近膨胀/压缩点然后执行大量操作,则压缩和/或膨胀很容易“蝴蝶”相当平衡的插入和删除。无论如何,祝你好运。
  • @WhozCraig - 和往常一样,很棒的例子可以作为答案发布。
猜你喜欢
  • 2016-06-06
  • 2012-09-19
  • 2014-07-21
  • 2013-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-04
相关资源
最近更新 更多