如何将项目动态添加到动态数组
How to add items dynamically to dynamic array
我正在尝试使用函数将项目添加到动态数组。我正在做的是计算数组的大小并将其增加 1。我的问题是计算数组的大小; sizeof(*tab) / sizeof((*tab)[0])
始终为零。
struct table
{
char key[20];
int val;
};
struct table *symTab, *insTab, *datTab;
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val) {
struct table temp;
strcpy(temp.key, key);
temp.val = val;
size_t space;
if (*tab == 0) {
space = 1;
*tab = malloc(space);
*tab[0] = temp;
return;
}
//#region for test only
size_t sizeOfTab = sizeof(*tab);//4
size_t sizeOfEntity1 = sizeof((*tab)[0]);//24
size_t sizeOfEntity2 = sizeof(**tab);//24
size_t sizeOfEntity3 = sizeof(struct table);//24
//#endregion
space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero
space++;
*tab = realloc(*tab, space);
*tab[space - 1] = temp;
}
int main()
{
addFieldToTable(&insTab, "mov", 0);
addFieldToTable(&insTab, "cmp", 1);
}
在我的代码中,第一项已成功添加,但当尝试添加第二项时计算的大小为零,因此数组不会调整大小,因此第一项 *tab[0]
被替换通过新项目。
如何解决这个计算数组大小的问题,使函数能够动态添加项目?。
您正在为具有 24B + 填充的结构分配 1B。
您要做的是为 sizeof
您的 struct
.
分配内存
*tab = malloc(sizeof(struct table));
sizeof
sizeof(*tab); // Is just size of pointer
sizeof(**tab); === sizeof(struct table); // It's size of Structure
如果您想知道数组有多少个元素,请传递另一个包含该信息的参数。
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements)
int noElements = 0;
addFieldToTable(&insTab, "mov", 0, noElements++);
addFieldToTable(&insTab, "cmp", 1, noElements++);
addFieldToTable(&insTab, "third", 1, noElements++);
重新分配是一项昂贵的操作,例如,您应该为 100 个结构分配 space,然后,如果您需要更多 space,则为 200 个结构重新分配。
如果你搞不懂,你的代码有更正
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_KEY_LENGTH 20
struct table
{
char key[MAX_KEY_LENGTH];
int val;
};
struct table *symTab, *insTab, *datTab;
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements) {
struct table temp;
strcpy_s(temp.key, key);
temp.val = val;
if (*tab == NULL)
{
*tab = malloc(sizeof(struct table));
(*tab)[0] = temp;
return;
}
*tab = realloc(*tab, ((noElements + 1) * sizeof(struct table)));
(*tab)[noElements] = temp;
}
int main()
{
int noElements = 0;
addFieldToTable(&insTab, "mov", 0, noElements++);
addFieldToTable(&insTab, "cmp", 1, noElements++);
addFieldToTable(&insTab, "third", 1, noElements++);
for (int i = 0; i < noElements; i++)
{
printf("\n%d: %s, %d", i+1, insTab[i].key, insTab[i].val);
}
return 0;
}
输出
1: mov, 0
2: cmp, 1
3: third, 1
您可能让事情变得比他们需要的更难。对于初学者,您始终可以减少跟踪当前分配的 table
结构数量所需的工作,方法是在 data 周围添加一个包含计数器的简单包装器。您也可以在这里使用 typedef
来发挥您的优势。
例如,与其直接处理 table
,不如将 table
的元素移动到它自己的一个简单结构中,比如 tbl_data
并简单地在 table
到数据和分配的 tbl_data
元素数的计数器?它可以像这样工作:
typedef struct {
char key[MAX_KEY_LENGTH];
int val;
} tbl_data;
typedef struct { /* simple wrapper preserving allocation size in ntbl */
tbl_data *tbl;
size_t ntbl;
} table;
接下来,避免使用全局变量。您已经将 **tab
作为参数传递,无需将 insTab
声明为全局参数。在 main()
中声明它作为参数传递,就像您打算做的那样。 (注意: 使用包装器,您可以使用 table
的简单静态实例并将其作为 *tab
传递)
想想上面的结构方案。您只为添加的每个 tbl_data
分配,并且在 分配 和 验证 新内存之后,您只需分配 temp
到 tab->tbl[tab->ntbl]
并将 tbl_data
(tab->ntbl
) 的数量增加 1。简单得多。由于每个 addFieldToTable
都增加了 1
的分配,因此您可以只对第一个和所有后续分配使用 realloc
来简化 addFieldToTable
。
(注意: 为每个添加的元素重复调用 malloc
(或 realloc
或 calloc
)并不是一个非常有效的分配方案, 但暂时没问题。你可以在 tbl->ntbl
达到限制时分配 32
、512
和 realloc
的块,或者每次都加倍 tbl->ntbl
和 realloc
当您达到新限制以减少 realloc
调用时)
如果您在 main()
中声明 table insTab = { NULL, .ntbl = 0 };
,您的 addFieldToTable
经过改进的验证可以减少为:
void addFieldToTable (table *tab, char *key, int val)
{
if (!key) { /* validate key is not NULL */
fprintf (stderr, "error: key parameter is NULL\n");
return;
}
tbl_data temp;
size_t keylen = strlen (key); /* get the length of key */
if (keylen > MAX_KEY_LENGTH) { /* truncate if > MAX_KEY_LENGTH */
key[keylen - 1] = 0;
fprintf (stderr, "warning: key exceeds %d, truncated.\n",
MAX_KEY_LENGTH);
}
strcpy (temp.key, key);
temp.val = val;
/* just realloc tbl_data, since you are increasing by 1 each time */
void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));
if (!tmptbl) { /* validate realloc succeeded or handle error */
fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
tab->ntbl);
return;
}
tab->tbl = tmptbl; /* assign new block to *tab */
tab->tbl[tab->ntbl] = temp; /* assign temp */
tab->ntbl++; /* increment ntbl */
}
(注意: 你真的应该将函数类型更改为 tbl_data*
和 return 指向 tab->tbl
的指针,这样你就可以 (1 ) return success/failure 的指示,例如指针或 NULL
;以及 (2) 使指向新的 tbl_data
的指针立即可用。您也可以将其声明为输入 int
和 return 1
表示成功,输入 0
表示失败并保留上面 (1) 的好处)
总而言之,您可以执行类似于以下操作的操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_KEY_LENGTH 20
typedef struct {
char key[MAX_KEY_LENGTH];
int val;
} tbl_data;
typedef struct { /* simple wrapper preserving allocation size in ntbl */
tbl_data *tbl;
size_t ntbl;
} table;
void addFieldToTable (table *tab, char *key, int val)
{
if (!key) { /* validate key is not NULL */
fprintf (stderr, "error: key parameter is NULL\n");
return;
}
tbl_data temp;
size_t keylen = strlen (key); /* get the length of key */
if (keylen > MAX_KEY_LENGTH) { /* truncate if > MAX_KEY_LENGTH */
key[keylen - 1] = 0;
fprintf (stderr, "warning: key exceeds %d, truncated.\n",
MAX_KEY_LENGTH);
}
strcpy (temp.key, key);
temp.val = val;
/* just realloc tbl_data, since you are increasing by 1 each time */
void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));
if (!tmptbl) { /* validate realloc succeeded or handle error */
fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
tab->ntbl);
return;
}
tab->tbl = tmptbl; /* assign new block to *tab */
tab->tbl[tab->ntbl] = temp; /* assign temp */
tab->ntbl++; /* increment ntbl */
}
int main (void)
{
table insTab = { NULL, .ntbl = 0 };
addFieldToTable (&insTab, "mov", 0);
addFieldToTable (&insTab, "cmp", 1);
addFieldToTable (&insTab, "next command", 1234);
if (insTab.ntbl == 0) {
fprintf (stderr, "error: insTab is empty.\n");
exit (EXIT_FAILURE);
}
for (size_t i = 0; i < insTab.ntbl; i++)
printf ("%-*s : %d\n", MAX_KEY_LENGTH,
insTab.tbl[i].key, insTab.tbl[i].val);
free (insTab.tbl); /* don't forget to free allocated memory */
return 0;
}
例子Use/Output
$ ./bin/struct_table
mov : 0
cmp : 1
next command : 1234
内存Use/Error检查
在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址,因此,(2) 当不再需要时可以释放。
您必须使用内存错误检查程序来确保您不会尝试写入 beyond/outside 已分配内存块的边界、尝试读取或基于未初始化值的条件跳转,最后,确认您释放了所有已分配的内存。
对于Linux valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。
$ valgrind ./bin/struct_table
==4003== Memcheck, a memory error detector
==4003== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4003== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==4003== Command: ./bin/struct_table
==4003==
mov : 0
cmp : 1
next command : 1234
==4003==
==4003== HEAP SUMMARY:
==4003== in use at exit: 0 bytes in 0 blocks
==4003== total heap usage: 3 allocs, 3 frees, 144 bytes allocated
==4003==
==4003== All heap blocks were freed -- no leaks are possible
==4003==
==4003== For counts of detected and suppressed errors, rerun with: -v
==4003== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放所有分配的内存并且没有内存错误。
检查一下,如果您有任何问题,请告诉我。
这行space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero
确实很烂!
tab
被声明为一个struct table **
,所以*tab
是一个指针而sizeof(*tab)
只是一个指针的大小...假设struct table
包含多个单个元素,很可能是sizeof(struct table) > sizeof(struct table *)
,所以整数除法一般为0。
因此您必须自己保留分配的大小(您无法直接从 C 获取它)。但是无论如何,realloc
可能是一个昂贵的操作,所以你应该分块分配并保持分配的大小和使用的大小。
我正在尝试使用函数将项目添加到动态数组。我正在做的是计算数组的大小并将其增加 1。我的问题是计算数组的大小; sizeof(*tab) / sizeof((*tab)[0])
始终为零。
struct table
{
char key[20];
int val;
};
struct table *symTab, *insTab, *datTab;
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val) {
struct table temp;
strcpy(temp.key, key);
temp.val = val;
size_t space;
if (*tab == 0) {
space = 1;
*tab = malloc(space);
*tab[0] = temp;
return;
}
//#region for test only
size_t sizeOfTab = sizeof(*tab);//4
size_t sizeOfEntity1 = sizeof((*tab)[0]);//24
size_t sizeOfEntity2 = sizeof(**tab);//24
size_t sizeOfEntity3 = sizeof(struct table);//24
//#endregion
space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero
space++;
*tab = realloc(*tab, space);
*tab[space - 1] = temp;
}
int main()
{
addFieldToTable(&insTab, "mov", 0);
addFieldToTable(&insTab, "cmp", 1);
}
在我的代码中,第一项已成功添加,但当尝试添加第二项时计算的大小为零,因此数组不会调整大小,因此第一项 *tab[0]
被替换通过新项目。
如何解决这个计算数组大小的问题,使函数能够动态添加项目?。
您正在为具有 24B + 填充的结构分配 1B。
您要做的是为 sizeof
您的 struct
.
*tab = malloc(sizeof(struct table));
sizeof
sizeof(*tab); // Is just size of pointer
sizeof(**tab); === sizeof(struct table); // It's size of Structure
如果您想知道数组有多少个元素,请传递另一个包含该信息的参数。
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements)
int noElements = 0;
addFieldToTable(&insTab, "mov", 0, noElements++);
addFieldToTable(&insTab, "cmp", 1, noElements++);
addFieldToTable(&insTab, "third", 1, noElements++);
重新分配是一项昂贵的操作,例如,您应该为 100 个结构分配 space,然后,如果您需要更多 space,则为 200 个结构重新分配。
如果你搞不懂,你的代码有更正
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_KEY_LENGTH 20
struct table
{
char key[MAX_KEY_LENGTH];
int val;
};
struct table *symTab, *insTab, *datTab;
void addFieldToTable(struct table **tab, char key[MAX_KEY_LENGTH], int val, int noElements) {
struct table temp;
strcpy_s(temp.key, key);
temp.val = val;
if (*tab == NULL)
{
*tab = malloc(sizeof(struct table));
(*tab)[0] = temp;
return;
}
*tab = realloc(*tab, ((noElements + 1) * sizeof(struct table)));
(*tab)[noElements] = temp;
}
int main()
{
int noElements = 0;
addFieldToTable(&insTab, "mov", 0, noElements++);
addFieldToTable(&insTab, "cmp", 1, noElements++);
addFieldToTable(&insTab, "third", 1, noElements++);
for (int i = 0; i < noElements; i++)
{
printf("\n%d: %s, %d", i+1, insTab[i].key, insTab[i].val);
}
return 0;
}
输出
1: mov, 0
2: cmp, 1
3: third, 1
您可能让事情变得比他们需要的更难。对于初学者,您始终可以减少跟踪当前分配的 table
结构数量所需的工作,方法是在 data 周围添加一个包含计数器的简单包装器。您也可以在这里使用 typedef
来发挥您的优势。
例如,与其直接处理 table
,不如将 table
的元素移动到它自己的一个简单结构中,比如 tbl_data
并简单地在 table
到数据和分配的 tbl_data
元素数的计数器?它可以像这样工作:
typedef struct {
char key[MAX_KEY_LENGTH];
int val;
} tbl_data;
typedef struct { /* simple wrapper preserving allocation size in ntbl */
tbl_data *tbl;
size_t ntbl;
} table;
接下来,避免使用全局变量。您已经将 **tab
作为参数传递,无需将 insTab
声明为全局参数。在 main()
中声明它作为参数传递,就像您打算做的那样。 (注意: 使用包装器,您可以使用 table
的简单静态实例并将其作为 *tab
传递)
想想上面的结构方案。您只为添加的每个 tbl_data
分配,并且在 分配 和 验证 新内存之后,您只需分配 temp
到 tab->tbl[tab->ntbl]
并将 tbl_data
(tab->ntbl
) 的数量增加 1。简单得多。由于每个 addFieldToTable
都增加了 1
的分配,因此您可以只对第一个和所有后续分配使用 realloc
来简化 addFieldToTable
。
(注意: 为每个添加的元素重复调用 malloc
(或 realloc
或 calloc
)并不是一个非常有效的分配方案, 但暂时没问题。你可以在 tbl->ntbl
达到限制时分配 32
、512
和 realloc
的块,或者每次都加倍 tbl->ntbl
和 realloc
当您达到新限制以减少 realloc
调用时)
如果您在 main()
中声明 table insTab = { NULL, .ntbl = 0 };
,您的 addFieldToTable
经过改进的验证可以减少为:
void addFieldToTable (table *tab, char *key, int val)
{
if (!key) { /* validate key is not NULL */
fprintf (stderr, "error: key parameter is NULL\n");
return;
}
tbl_data temp;
size_t keylen = strlen (key); /* get the length of key */
if (keylen > MAX_KEY_LENGTH) { /* truncate if > MAX_KEY_LENGTH */
key[keylen - 1] = 0;
fprintf (stderr, "warning: key exceeds %d, truncated.\n",
MAX_KEY_LENGTH);
}
strcpy (temp.key, key);
temp.val = val;
/* just realloc tbl_data, since you are increasing by 1 each time */
void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));
if (!tmptbl) { /* validate realloc succeeded or handle error */
fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
tab->ntbl);
return;
}
tab->tbl = tmptbl; /* assign new block to *tab */
tab->tbl[tab->ntbl] = temp; /* assign temp */
tab->ntbl++; /* increment ntbl */
}
(注意: 你真的应该将函数类型更改为 tbl_data*
和 return 指向 tab->tbl
的指针,这样你就可以 (1 ) return success/failure 的指示,例如指针或 NULL
;以及 (2) 使指向新的 tbl_data
的指针立即可用。您也可以将其声明为输入 int
和 return 1
表示成功,输入 0
表示失败并保留上面 (1) 的好处)
总而言之,您可以执行类似于以下操作的操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_KEY_LENGTH 20
typedef struct {
char key[MAX_KEY_LENGTH];
int val;
} tbl_data;
typedef struct { /* simple wrapper preserving allocation size in ntbl */
tbl_data *tbl;
size_t ntbl;
} table;
void addFieldToTable (table *tab, char *key, int val)
{
if (!key) { /* validate key is not NULL */
fprintf (stderr, "error: key parameter is NULL\n");
return;
}
tbl_data temp;
size_t keylen = strlen (key); /* get the length of key */
if (keylen > MAX_KEY_LENGTH) { /* truncate if > MAX_KEY_LENGTH */
key[keylen - 1] = 0;
fprintf (stderr, "warning: key exceeds %d, truncated.\n",
MAX_KEY_LENGTH);
}
strcpy (temp.key, key);
temp.val = val;
/* just realloc tbl_data, since you are increasing by 1 each time */
void *tmptbl = realloc (tab->tbl, (tab->ntbl + 1) * sizeof *(tab->tbl));
if (!tmptbl) { /* validate realloc succeeded or handle error */
fprintf (stderr, "error: memory exhausted at '%zu' elements.\n",
tab->ntbl);
return;
}
tab->tbl = tmptbl; /* assign new block to *tab */
tab->tbl[tab->ntbl] = temp; /* assign temp */
tab->ntbl++; /* increment ntbl */
}
int main (void)
{
table insTab = { NULL, .ntbl = 0 };
addFieldToTable (&insTab, "mov", 0);
addFieldToTable (&insTab, "cmp", 1);
addFieldToTable (&insTab, "next command", 1234);
if (insTab.ntbl == 0) {
fprintf (stderr, "error: insTab is empty.\n");
exit (EXIT_FAILURE);
}
for (size_t i = 0; i < insTab.ntbl; i++)
printf ("%-*s : %d\n", MAX_KEY_LENGTH,
insTab.tbl[i].key, insTab.tbl[i].val);
free (insTab.tbl); /* don't forget to free allocated memory */
return 0;
}
例子Use/Output
$ ./bin/struct_table
mov : 0
cmp : 1
next command : 1234
内存Use/Error检查
在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址,因此,(2) 当不再需要时可以释放。
您必须使用内存错误检查程序来确保您不会尝试写入 beyond/outside 已分配内存块的边界、尝试读取或基于未初始化值的条件跳转,最后,确认您释放了所有已分配的内存。
对于Linux valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。
$ valgrind ./bin/struct_table
==4003== Memcheck, a memory error detector
==4003== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4003== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==4003== Command: ./bin/struct_table
==4003==
mov : 0
cmp : 1
next command : 1234
==4003==
==4003== HEAP SUMMARY:
==4003== in use at exit: 0 bytes in 0 blocks
==4003== total heap usage: 3 allocs, 3 frees, 144 bytes allocated
==4003==
==4003== All heap blocks were freed -- no leaks are possible
==4003==
==4003== For counts of detected and suppressed errors, rerun with: -v
==4003== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放所有分配的内存并且没有内存错误。
检查一下,如果您有任何问题,请告诉我。
这行space = sizeof(*tab) / sizeof((*tab)[0]);//always gets zero
确实很烂!
tab
被声明为一个struct table **
,所以*tab
是一个指针而sizeof(*tab)
只是一个指针的大小...假设struct table
包含多个单个元素,很可能是sizeof(struct table) > sizeof(struct table *)
,所以整数除法一般为0。
因此您必须自己保留分配的大小(您无法直接从 C 获取它)。但是无论如何,realloc
可能是一个昂贵的操作,所以你应该分块分配并保持分配的大小和使用的大小。