内存分配/重新分配问题

3
我刚刚解决了我正在编写的程序中的内存分配问题,但我对我所做的修复工作并不满意。
在我的程序中,我正在建立一个结构体数组,并在每次想要向其添加结构体时重新分配数组的空间。以下是我的结构体的通用版本以及向数组添加结构体的函数:
typedef struct Example {
    const char* name;
    int (*func)(int, int);
    int bool_switch;
}

int add_struct_to_array( Example **example_array, int *ex_array_size, int name, int (*func)(int, int), int bool_switch)
{
    // first, make a new struct
    Example *new_example = (Example *) calloc( 1, sizeof( Example ) );
    if( new_example != NULL ) {
        new_example->name = name;
        new_example->func = func;
        new_example->bool_switch = bool_switch;
        ( *ex_array_size )++;
    } else {
        printf( "Errror allocating %s\n", name );
        exit( -1 );
    }

    // now, realloc the array of structs and add the new member to it
    Example **temp_example_array = ( Example** )realloc( example_array, ( *ex_array_size ) * sizeof( Example* ) );
    if( temp_example_array != NULL ) {
        example_array = temp_example_array;
        example_array[ ( *ex_array_size ) - 1 ] = new_example;
    } else {
        printf( "Reallocation failed\n" )
        exit( -1 );
    }
    return 0;
}

这是我将调用函数的地方(请注意,我最初分配了结构体数组,因为问题就出在那里)。

#include "example_struct.h"

int main( int argc, char **argv )
{
    int ex_array_size = 0;
    Example **example_array = ( Example** )calloc( 0, sizeof( Example* ) );

    add_struct_to_array( example_array, &ex_array_size, "name", &function, 1 );
    ...
    ...
    add_struct_to_array( example_array, &ex_array_size, "other_name", &other_func, 0 );

    /* Do stuff here */

    example_array_free( example_array );

    return 0;
}

在我的无知中,我认为用大小为0的数组进行分配是可以的,因为它最初是空的,之后我可以添加结构体。显然,这不起作用,我会得到关于“error for object 0x100100080: pointer being reallocated was not allocated” 的运行时错误。example_array位于地址0x100100080,并且我将分配给第一个结构体的地址为0x100100090,在几次重新分配后,example_array会用完空间。
所以,最终的问题是什么?我通过为example_array分配比需要更多的空间来解决这个问题,但这似乎非常不优雅。有更好的方法吗?
**编辑**
好的,从大多数回答的外观来看,我不应该使用指向指针的指针。因此,我正在尝试稍微不同的方法,混合pmg和crypto的响应。现在,这是我的代码:
/* example_struct.h */
int add_struct_to_array( Example *example_array, int *ex_array_size, int name, int (*func)(int, int), int bool_switch)
{
    Example temp_example_array = realloc( example_array, ( ( *ex_array_size ) + 1 ) * sizeof( Example ) );

    if( temp_example_array != NULL ) {
        example_array = temp_example_array;
        Example new_example;
        new_example.name = name;
        new_example.func = func;
        new_example.bool_switch = bool_switch;
        example_array[ ( *ex_array_size ) ] = new_example;
        ++( *ex_array_size );
    } else {
        fprintf( stderr, "Error reallocating for %s", name );
        exit( -1 );
    }
    return 0;
}



/* main.c */
...
...
#include "example_struct.h"
int main( int argc, char **argv )
{
    int ex_array_size = 0;
    Example *example_array = NULL;

    add_struct_to_array( example_array, &ex_array_size, "name", &func, 1 );
    add_struct_to_array( ... );
    ...
    add_struct_to_array( example_array, &ex_array_size, "other name", &other_func, 0 );

    example_free( example_array );
}

所有代码都能编译通过并且realloc也没有问题,但是我无法访问数组中的结构体。

/* main.c */
...
...
#include "example_struct.h"
int main( int argc, char **argv )
{
    int ex_array_size = 0;
    Example *example_array = NULL;

    add_struct_to_array( example_array, &ex_array_size, "name", &func, 1 );
    add_struct_to_array( ... );
    ...
    add_struct_to_array( example_array, &ex_array_size, "other name", &other_func, 0 );


    printf( "%s\n", example_array[0].name ) /* Segfault */


    example_free( example_array );
}

感谢您的帮助。再次感谢。

为什么需要一个指向指针的指针来示例?单个指针足以创建所需大小的“数组”。 - pmg
在add_struct_to_array()函数的第2行中,类型转换应该使用(Example*),而不是(example*),对吗? - user191776
提示:您不需要显式地对calloc、malloc或realloc返回的void *进行类型转换。阅读此内容:https://dev59.com/XUrSa4cB1Zd3GeqPX55J - user191776
1
@pmg:该数组存储的是指向结构体的指针,而不是结构体本身。(当然,直接将结构体存储在数组中可能更有效率。) - Porculus
1
@W_P,名字不是整数吗?使用%s打印它会导致错误。 - user191776
在代码中,它是一个const char *,我在这里只是打错了:/ - user35288
4个回答

4

realloc函数在指针值为NULL的情况下能够很好地工作...并会进行malloc操作。

*p = NULL;
new = realloc(p, 42); /* same as new = malloc(42); */
if (!new) { /* error */ }
p = new;

因此,忘记使用 calloc(反正您之后会覆盖零),将指针初始化为NULL,并随意使用 realloc

int main(void) {
    Example *example_array = NULL;
    add_struct_to_array(&example_array, &ex_array_size, "name", function, 1);
    /* ... */
    free(example_array);
}

使用NULL的巧妙技巧。从realloc的手册页面上可以看到:“除非ptr是NULL,否则它必须由先前调用malloc(),calloc()或realloc()返回。” - user191776
而在此之前,它(man 3 calloc)说:“如果ptr为NULL,则调用等效于malloc(size)”。 - pmg
我稍微修改了一下我的代码,能看一下我编辑过的问题吗? - user35288
在您的新版本中,您必须将example_array的地址传递给add_struct_to_array函数:add_struct_to_array(&example_array, ...),并在成功重新分配后在函数内部进行更改。 - pmg

0

看起来 std::vector 完全符合您的需求。它像数组一样快速,并知道如何管理内存。以下是一个简单的示例。

向量使用与数组相同的连续内存。如果您知道需要多少个 Example 对象,则可以在构造函数中告诉向量它需要多大。如果您不知道会有多少个对象,向量将增长以满足您的需求。如果性能很重要,则尝试在构造函数中分配总内存量,因为当向量重新分配时,它将创建一个新的连续内存块,然后将每个对象复制到新的内存区域中(这时 Examples 构造函数和析构函数将被执行)。

查看向量的不同构造函数、capacity() 和 reserve() here

#include <vector>

int function_1(int a, int b) {
    return 100;
}

int function_2(int a, int b) {
    return 200;
}

typedef struct  {
    int name;
    int (*func)(int, int);
    int bool_switch;
} example_t;

typedef std::vector<example_t> example_container_t;

int main() {
    example_container_t container;

    example_t example_1;
    example_1.name = 1;
    example_1.func = &function_1;
    example_1.bool_switch = true;
    container.push_back(example_1);

    example_t example_2;
    example_2.name = 1;
    example_2.func = &function_1;
    example_2.bool_switch = true;
    container.push_back(example_2);
    return 0;
}

2
你为什么认为 W_P 想要将他的代码改成 C++ 呢? :) - pmg
1
源代码非常像示例代码(即示例类,没有真正的函数,无法编译等)。我认为W_P可能在这个早期阶段愿意接受建议。@W_P - 如果不是这种情况,请原谅我的误解。 - skimobear

0
尝试以下更改。您不需要分配任何额外的空间。
编辑:添加由pmg和Bart van Ingen Schenau建议的更改。
int add_struct_to_array( Example ***example_array, int *ex_array_size, int name, int (*func)(int, int), int bool_switch)
{
     Example **temp_example_array = realloc(*example_array,((*ex_array_size) + 1) * sizeof(Example *) );
     Example *new_example = calloc(1, sizeof( Example ) );

    if( temp_example_array != NULL && new_example != NULL ) {
        *example_array = temp_example_array;
        *example_array[ *ex_array_size ] = new_example;
        new_example->name = name;
        new_example->func = func;
        new_example->bool_switch = bool_switch;
        ( *ex_array_size )++;
    } else {
        printf( "Error allocating %s\n", name );
        exit( -1 );
    }
    return 0;
}


#include "example_struct.h"

int main( int argc, char **argv )
{
    int ex_array_size = 0;
    Example **example_array = calloc( 0, sizeof( Example* ) );

    add_struct_to_array( &example_array, &ex_array_size, "name", &function, 1 );
    ...

    add_struct_to_array( &example_array, &ex_array_size, "other_name", &other_func, 0 );

    ...

    example_array_free( example_array );

    return 0;
}

为了避免破坏您的其他代码,我使用了指向指针的 example_array。不过,更好的解决方案是简单地使用指向结构体的指针,并继续对其进行 realloc() 扩展空间。

2
原则上,永远不要将realloc的结果分配给您正在重新分配的指针。这样做会在realloc失败时导致内存泄漏,因为您会用NULL覆盖旧值。 - pmg
但是在加密分配realloc的结果给他正在重新分配的指针时,他受到if(temp_arry != NULL)的保护,因此如果realloc失败,它不会覆盖任何东西... - user35288
我已经对我的代码进行了一些更改,你能看一下我编辑后的问题吗? - user35288
1
很遗憾,add_struct_to_array函数无法正常工作。在main函数中,example_array变量从未被realloc的结果更新,因为该函数只是改变了变量的副本 - Bart van Ingen Schenau
这怎么样?example_array只是指向内存空间的指针,不是吗? - user35288
显示剩余3条评论

0
这是一个最小工作版本(使用了很多C++关键字作为标识符,我有点抱歉,但当我开始时觉得很有趣,无法在中途停下或返回)它也可以在ideone上运行(http://ideone.com/iMByR)。
#include <stdio.h>
#include <stdlib.h>

struct protected {
  int this;
  int (*catch)(int, int);
  int friend;
};

int catch(int mutable, int virtual) {
  return mutable + virtual;
}

struct protected *add_one(struct protected **private,
                          int *explicit, int using,
                          int (*catch)(int, int), int friend) {
  struct protected *new;

  new = realloc(*private, (*explicit + 1) * sizeof *new);
  if (new) {
    *private = new;
    (*private)[*explicit].this = using;
    (*private)[*explicit].catch = catch;
    (*private)[*explicit].friend = friend;
    (*explicit)++;
  }
  return new;
}

/* create an array of structs using dynamic memory */
/* keep adding elements to it, and growing it as needed */
int main(void) {
  int using;
  /* explicit contains the number of elements in the try array */
  int explicit = 0;
  struct protected *try = NULL;

  /* create and grow */
  for (using = 0; using < 7; using++) {
    if (add_one(&try, &explicit, using + 1, catch, 0) == NULL) {
      fprintf(stderr, "failure at loop %d\n", using);
      exit(EXIT_FAILURE);
    }
  }

  /* verify */
  for (using = 0; using < explicit; using++) {
    printf("%d: %d\n", using, try[using].this);
  }

  free(try);
  return 0;
}

哈!我有几乎相同的东西,除了在add_one函数中的那些行,你有(*private)[*explicit].this = ...etc。我没有在括号中使用*private,结果得到了一个段错误...感谢您的帮助,顺便说一下,我喜欢将C++关键字用作标识符! - user35288
呵呵,不要经常这样做(将C++关键字作为C标识符):这会让你创造更多的敌人而不是朋友 :D - pmg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接