C语言 结构 realloc 中的字符串动态数组



>我在一个结构中有一个字符串数组,看起来像这样

#define ID_LEN 20
struct Person{
char name[ID_LEN];
int num_items;
char **items;
};
int main(){
int num_persons = 10;
struct Person *ptr[num_persons];

我从10个人开始。最初所有人都有 0 个项目,所以他们的项目列表是 malloc(0(。

for(int i = 0; i < num_persons; i++){
        ptr[i] = (struct Person *) malloc(sizeof(struct Person) + 1);
        ptr[i]->num_items = 0;
        ptr[i]->items = malloc(0 * sizeof(char*));
}

在某些时候,我想命名这些人并添加一些这样的项目。

strcpy(ptr[0]->name, "John");
ptr[0]->num_items = 1;
ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));
strcpy(ptr[0]->items[0], "pencil");

printf("Name: %sn", ptr[0]->name);
printf("Number of items: %dn", ptr[0]->num_items);
for(int i = 0; i < ptr[0]->num_items; i++){
    printf("Item %d is %sn", i, ptr[0]->items[i]);
}

我遇到分段错误:11。我不确定我是否正确地完成了 realloc。

你在这里有几个问题

首先是你的ID_LEN

没有任何地方检查你是否通过复制到名称你超过了ID_LEN

所以而不是

strcpy(ptr[0]->name, "John"); 

strcpy_s(ptr[0]->name,sizeof(ptr[0]->name),"John or whatever"); // C11

您分配

ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))

不应强制转换 malloc/realloc 的返回值(在 Web 上搜索解释,它已被描述为恶心(

使用 realloc 时,应先检查返回值,可能会失败。

char** tmp = realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))
if (tmp != NULL)
{
  ptr[0]->items = tmp;
}
else
{
  abort();
}

内存 realloc 返回的部分未初始化(旧指针仍然存在,但新的未初始化。在您的情况下,您之前没有任何指针,以便一个items[0]未初始化。

所以当你这样做时

strcpy(ptr[0]->items[0], "pencil");

它将失败,因为items[0]指向某个任意内存位置。

realloc指针后,您需要初始化它们以指向内存大到足以容纳字符串

例如

ptr[0]->items[0] = strdup("pencil");  // does malloc then copies string

每次需要添加新的 realloc 时都使用 realloc 效率不高项目,而是分配一堆项目,但设置您不使用的项目到 NULL,然后跟踪还剩下多少,一旦它们用完分配另一堆

代码中的所有问题都围绕着使用未分配的内存。考虑

 ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));//line 1
 strcpy(ptr[0]->items[0], "pencil");//line 2

在第 1 行中,您已分配内存来保存ptr[0]->num_items数量的指向 c 字符串的指针。但是您实际上没有分配内存来存储 c 字符串,即这些指针不指向实际内存。在第 2 行中时,您尝试访问它只是ptr[0]->items[0] char*没有为其分配内存。我在第 2 行之前添加了以下行,您的代码工作正常。

 for(int i=0;i<ptr[0]->num_items;i++)
      ptr[0]->items[i]=malloc(sizeof(char)*10);

除了其他答案提供的内容外,还有其他几个与您正在做的事情相关的注意事项。首先,不要0分配任何东西。这是过去几天的遗留物,没有必要。

接下来,在静态声明可变长度的 num_persons 指针数组以键入 struct Person 之后,您必须决定是一次为所有指针分配存储,还是仅在添加人员时为结构分配存储。当您将代码拆分为函数时,这将产生影响。

由于数据结构的其余部分将动态分配,因此您必须在复制/初始化之前验证每个分配是否成功。

分配空结构时,请仔细查看calloc而不是malloc。这是 calloc 提供的默认初始化可以提供帮助的一个实例。

任务的其余部分只是一个记帐问题 - 即核算哪些成员需要分配,每个分配的当前状态和大小,然后最终在不再需要时释放已分配的内存。

以您可能想要的代码为例,将带有项目的人添加到指针数组中。若要验证添加,您需要知道 ptr 是有效的指针,以及您是否已达到num_persons限制,然后再开始分配内存。接下来,您必须为struct Person分配存储,并将该块的地址分配给ptx[X](对于您正在使用的任何索引X(。

分配后,您可以初始化默认值(或者如果使用了calloc,则知道所有值都已初始化为 0/NULL (。之后,您可以复制name(限制为 ID_LEN 个(,并为您希望在指针到指针到字符* items中保存的每个项目分配指针存储(您将处理类似于ptr接受也分配items的指针( 请注意,此时无需realloc (items,...,因为这是第一次分配items

如果将所有这些放在一起,则可以想出一个函数,将人员与其第一项相加,类似于以下内容,其中max是允许的最大人数nm是姓名,itm是项目,idx是指向该人员当前索引的指针:

/* add person with item */
struct person *addpwitem (struct person **ptr, int max, char *nm,
                            char *itm, int *idx)
{
    if (!ptr || *idx + 1 == max) return NULL;
    int i = *idx;
    /* allocate storage for struct */
    if (!(ptr[i] = calloc (1, sizeof **ptr))) {
        fprintf (stderr, "error: ptr[%d], virtual memory exhausted.n", i);
        return NULL;
    }
    strncpy (ptr[i]->name, nm, ID_LEN);  /* copy name */
    /* allocate/validate pointer to char* ptr[i]->items  */
    if (!(ptr[i]->items = 
        malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.n");
        return NULL;
    }
    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->num_items++;
    (*idx)++;
    return ptr[i];
}

(注意:您必须在调用函数中将函数的返回分配给ptr[X]。另请注意,C 样式建议使用所有小写名称,这就是您在上面看到的,将驼峰大小写名称留给 C++(

添加人员后,您将希望能够将项目添加到与该人员关联的项目列表中。这就是realloc的用武之地,允许您调整指向该人员的项目的指针数量。您可以执行类似操作来添加 person,但您传递的索引不再需要是指针,因为它不会在函数中更新。例如

/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
    if (!ptr) return NULL;
    void *tmp;
    /* allocate/realloc/validate pointer to char* ptr[i]->items  */
    if (!(tmp = realloc (ptr[i]->items,
                        (ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->items = tmp;    /* assign tmp on successful realloc */
    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->num_items++;
    return ptr[i];
}

现在,您可以将人员和项目添加到列表中,但如果要使用它们,则需要一种方法来循环访问列表中的所有值。无论是搜索、打印还是释放内存,所有迭代函数都将具有类似的形式。要打印所有人员和物品,您可以执行类似于以下内容的操作:

/* print the list of persons and items */
void prndetail (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        printf (" %-20s   %2d", ptr[p]->name, ptr[p]->num_items);
        for (i = 0; i < ptr[p]->num_items; i++)
            printf ("%s%s", i ? ",  " : " ", ptr[p]->items[i]);
        putchar ('n');
    }
}

在动态分配内存的任何代码中,对于分配的任何内存块,您都有 2 个责任:(1( 始终保留指向内存块起始地址的指针,以便 (2( 当不再需要内存块时可以释放它。完成列表后,您需要free它。与打印类似,可以按如下方式完成:

/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        for (i = 0; i < ptr[p]->num_items; i++)
            free (ptr[p]->items[i]);
        free (ptr[p]->items);
        free (ptr[p]);
    }
    // free (ptr);  /* if array of pointers allocated, not static */
}

这就是会计课的全部内容。如果您跟踪正在使用的数据结构的每个部分,则管理内存非常简单。将所有部分放在一个简短的示例中,您可以执行以下操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { NPER = 10, ID_LEN = 20 };
struct person {
    char name[ID_LEN];
    int num_items;
    char **items;
};
/* add person + item - to add both person and item at once */
struct person *addpwitem (struct person **ptr, int max, char *nm,
                            char *itm, int *idx);
/* add item to existing person */
struct person *additem (struct person **ptr, int i, char *itm);
void prndetail (struct person **ptr, int idx);
void deletelist (struct person **ptr, int idx);
int main (void) {
    int idx = 0;
    struct person *ptr[NPER];
    /* allocate storage for struct, add person + item */
    if (!(ptr[idx] = addpwitem (ptr, NPER, "John", "pencils", &idx))) {
        fprintf (stderr, "error: adding ptr[%d] failed.n", idx);
        return 1;
    }
    printf ("nadded John:n");
    prndetail (ptr, idx);       /* print contents of persons & items */
    additem (ptr, idx - 1, "pens"); /* add next item */
    printf ("nadded item 'pens' for John:n");
    prndetail (ptr, idx);       /* print contents of persons & items */
    /* add next person + item */
    if (!(ptr[idx] = addpwitem (ptr, NPER, "Martha", "paper", &idx))) {
        fprintf (stderr, "error: adding ptr[%d] failed.n", idx);
        return 1;
    }
    printf ("nadded Martha:n");
    prndetail (ptr, idx);       /* print contents of persons & items */
    deletelist (ptr, idx);      /* free all allocated memory */
    return 0;
}
/* add person with item */
struct person *addpwitem (struct person **ptr, int max, char *nm,
                            char *itm, int *idx)
{
    if (!ptr || *idx + 1 == max) return NULL;
    int i = *idx;
    /* allocate storage for struct */
    if (!(ptr[i] = calloc (1, sizeof **ptr))) {
        fprintf (stderr, "error: ptr[%d], virtual memory exhausted.n", i);
        return NULL;
    }
    strncpy (ptr[i]->name, nm, ID_LEN);  /* copy name */
    /* allocate/validate pointer to char* ptr[i]->items  */
    if (!(ptr[i]->items = 
        malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.n");
        return NULL;
    }
    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->num_items++;
    (*idx)++;
    return ptr[i];
}
/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
    if (!ptr) return NULL;
    void *tmp;
    /* allocate/realloc/validate pointer to char* ptr[i]->items  */
    if (!(tmp = realloc (ptr[i]->items,
                        (ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
        fprintf (stderr, "error: items*, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->items = tmp;    /* assign tmp on successful realloc */
    /* allocate/validate memory for ptr[num_items]->items[num_items] */
    if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
        fprintf (stderr, "error: items, virtual memory exhausted.n");
        return NULL;
    }
    ptr[i]->num_items++;
    return ptr[i];
}
/* print the list of persons and items */
void prndetail (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        printf (" %-20s   %2d", ptr[p]->name, ptr[p]->num_items);
        for (i = 0; i < ptr[p]->num_items; i++)
            printf ("%s%s", i ? ",  " : " ", ptr[p]->items[i]);
        putchar ('n');
    }
}
/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
    if (!ptr || !*ptr) return;
    int i, p;
    for (p = 0; p < idx; p++) {
        for (i = 0; i < ptr[p]->num_items; i++)
            free (ptr[p]->items[i]);
        free (ptr[p]->items);
        free (ptr[p]);
    }
    // free (ptr);  /* if array of pointers allocated */
}

示例使用/输出

$ ./bin/struct_p2p2c
added John:
 John                    1 pencils
added item 'pens' for John:
 John                    2 pencils,  pens
added Martha:
 John                    2 pencils,  pens
 Martha                  1 paper

内存错误检查

您必须使用内存错误检查程序来确保您没有超出/超出分配的内存块,尝试读取或基于未初始化值进行跳转,最后确认您已释放所有已分配的内存。

这很简单:

$ valgrind ./bin/struct_p2p2c
==7618== Memcheck, a memory error detector
==7618== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7618== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==7618== Command: ./bin/struct_p2p2c
==7618==
added John:
 John                    1 pencils
added item 'pens' for John:
 John                    2 pencils,  pens
added Martha:
 John                    2 pencils,  pens
 Martha                  1 paper
==7618==
==7618== HEAP SUMMARY:
==7618==     in use at exit: 0 bytes in 0 blocks
==7618==   total heap usage: 8 allocs, 8 frees, 115 bytes allocated
==7618==
==7618== All heap blocks were freed -- no leaks are possible
==7618==
==7618== For counts of detected and suppressed errors, rerun with: -v
==7618== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

始终确认 所有堆块都已释放 - 没有泄漏的可能性,同样重要 错误摘要:0 个上下文中的 0 个错误

看看这个和其他答案。他们之间有很多好的建议。如果您有其他问题,请告诉我们。

您正在为项目列表分配空间,但不会为每个单独的项目字符串分配空间。你

strcpy(ptr[0]->items[0], "pencil");

将失败。

相关内容

  • 没有找到相关文章

最新更新