这是一个基本而重要的问题。您不仅需要知道如何处理动态分配,还需要知道如何以结构化的方式处理它。首先,您需要两个计数器。一个计数器跟踪您分配的整数数量,第二个计数器跟踪使用的整数数量。当你"remove"一个整数时,你不需要重新分配,只有在添加和used == allocated时才需要。否则,如果used < allocated,则无需为"add"之后的下一个整数分配。
理想情况下,您希望分配一些预期数量的整数,并且仅在最初分配的内存块中空间不足时重新分配(通常每次需要重新分配时将当前分配的块的大小加倍)以最小化数量realloc()1 的通话费用相对较高。它确实增加了一点复杂性。对于学习练习,为每个新的int 分配就可以了。
查看分配方案之前的另一个注意事项是输入scanf()。这导致了一个非常脆弱的输入方案。输入中的一个杂散字符将导致 matching-failure,其中错误字符在stdin 中未被读取,从而破坏了您的下一个(如果您依赖输入排序 - 您的其余部分)输入。使用面向行的 输入函数(如fgets() 或POSIX getline())读取输入要好得多,这样每次读取都会消耗整行输入。然后,您可以使用sscanf() 从fgets() 填充的缓冲区(字符数组)中解析所需的值。这样无论使用sscanf() 的转换是成功还是失败,stdin 中都不会留下未读的内容。
此外,避免使用全局变量。相反,在需要它们的范围内声明变量,然后将任何需要的信息作为参数传递给函数,而不是依赖全局变量。 (它们有其应有的位置——但在您学习 C 时找不到任何位置,除非您在微控制器上编程)
现在对于您的分配方案,将您的名称 number_of_elems 更改为 allocated 以跟踪分配给的整数数量并添加 used 以跟踪您分配的块中使用的 int 数量,您的方案将是:
- 初始化
ptr = NULL; 开始,
- 在读取
"add" 并使用strcmp() 进行确认后(您将C 中的字符串相等性与strcmp() 进行比较,而不是==),您将读取并验证随后的整数的转换,然后李>
- 比较
if (used == allocated) 以确定是否需要为新整数分配,如果需要的话
- 对所有分配使用
realloc()(当指针为NULL 时,realloc() 的行为类似于malloc()),
- 验证分配后,您递增
allocated += 1; 并将整数值存储到新的内存块后,您递增 used += 1;,
- 如果选择了
"remove",只需验证used > 0,如果是这样,只需减少used,此时无需调整分配——这只发生在@987654354 @。
- [可选] 在您阅读并完成
"add"/"remove" 循环后,您可以制作最后一个realloc() 以将分配的内存块调整为存储used 整数所需的大小。
那么,您将如何实现它?让我们从 main() 的声明开始,初始化所有变量:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 256 /* if you need a constant, #define one (or more) */
int main()
{
/* avoid global variables, initialize all vars (good practice) */
char action[MAXC] = ""; /* buffer (array) to hold every input */
int allocated = 0, /* number of integers allocated */
used = 0, /* number of integers used */
*ptr = NULL;
现在让我们看看您的读取循环并通过读取action 并确定我们是否需要"add" 一个值:
while (1) {
int b = 0; /* declare variables in scope needed */
/* control read-loop with function return, end if EOF or [Enter] on empty-line */
if (fgets (action, MAXC, stdin) == NULL || *action == '\n')
break;
action[strcspn (action, "\n")] = 0; /* trim '\n' from end of action */
if (strcmp (action, "add") == 0) { /* compare strings with strcmp() */
/* reused action to read integer value */
if (!fgets (action, MAXC, stdin) || *action == '\n')
break;
if (sscanf (action, "%d", &b) != 1) { /* validate conversion to int */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
此时,我们已经验证了所有输入和所有转换,并且我们知道我们需要将"add" 中的值b 分配给我们分配的块,其中包含我们将分配给指针的整数。但是请注意,由于我们使用的是realloc(),因此您总是将realloc() 指向一个临时指针,因此如果realloc() 未能返回NULL,您不要用NULL 覆盖您的指针地址,从而创建由于您的内存泄漏而导致的内存泄漏指针持有的地址丢失(现在不能传递给free() 来恢复内存)。所以当调用realloc() 时不要这样做:
ptr = realloc (ptr, size);
改为:
void *tmp = realloc (ptr, (allocated + 1) * sizeof *ptr);
if (tmp == NULL) { /* validate EVERY allocation */
perror ("realloc-tmp");
break; /* don't exit, ptr still good */
}
ptr = tmp; /* assign reallocaed block to ptr */
只有在验证重新分配成功后,才能将新分配的内存块分配给原始指针。如果realloc() 失败,您在ptr 中的原始分配地址保持不变并且仍然很好,您可以使用存储到该点的所有数据在该点简单地中断。
考虑到这一点,"add" 块的其余部分是:
/* realloc() behaves as malloc() if ptr is NULL, no need for separate malloc()
* (realloc() using temporary pointer to avoid mem-leak if realloc() fails)
*/
if (used == allocated) {
void *tmp = realloc (ptr, (allocated + 1) * sizeof *ptr);
if (tmp == NULL) { /* validate EVERY allocation */
perror ("realloc-tmp");
break; /* don't exit, ptr still good */
}
ptr = tmp; /* assign reallocaed block to ptr */
allocated += 1; /* increment after validation */
}
ptr[used] = b; /* assing b to ptr at index */
used += 1; /* increment after assignment */
/* debug output - remove if desired */
printf ("\nadding %d, used = %d, allocated = %d\n", b, used, allocated);
}
您的"remove" 块只是验证您有整数要删除,然后从used 中减去1。在if ... else if ... 块之后,您可以输出已分配块的当前使用内容:
else if (strcmp (action, "remove") == 0) { /* ditto strmcp() */
if (used > 0) /* only decrement used if > 0, and */
used -= 1; /* (no need to adjust allocation) */
/* debug output, remove if desired */
printf ("\nremove, used = %d, allocated = %d\n", used, allocated);
}
/* debug output (no need to duplicate in each if .. else ...) */
puts ("\ncurrent content");
for (int i = 0; i < used; i++)
printf ("%d\n", ptr[i]);
}
现在,可选在您的读取循环完成后,您可以将分配的大小调整为仅保存整数 used 所需的内存。这不是强制性的,通常甚至没有必要,但为了完整起见,您可以这样做:
/* [optional] final realloc to resize to exact size needed for used integers */
if (used < allocated) {
void *tmp = realloc (ptr, used * sizeof *ptr);
if (tmp) /* validate reallocation */
ptr = tmp; /* only assign after validation */
else /* otherwise just warn -- but original ptr still good */
perror ("realloc-ptr-to-used");
}
这基本上是你的程序,唯一剩下的就是使用你喜欢的存储值,然后在你完成释放分配的内存时调用free (ptr);。如果你把它放在一起,并在释放分配的内存之前输出完整的列表,你会:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 256 /* if you need a constant, #define one (or more) */
int main()
{
/* avoid global variables, initialize all vars (good practice) */
char action[MAXC] = ""; /* buffer (array) to hold every input */
int allocated = 0, /* number of integers allocated */
used = 0, /* number of integers used */
*ptr = NULL;
while (1) {
int b = 0; /* declare variables in scope needed */
/* control read-loop with function return, end if EOF or [Enter] on empty-line */
if (fgets (action, MAXC, stdin) == NULL || *action == '\n')
break;
action[strcspn (action, "\n")] = 0; /* trim '\n' from end of action */
if (strcmp (action, "add") == 0) { /* compare strings with strcmp() */
/* reused action to read integer value */
if (!fgets (action, MAXC, stdin) || *action == '\n')
break;
if (sscanf (action, "%d", &b) != 1) { /* validate conversion to int */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
/* realloc() behaves as malloc() if ptr is NULL, no need for separate malloc()
* (realloc() using temporary pointer to avoid mem-leak if realloc() fails)
*/
if (used == allocated) {
void *tmp = realloc (ptr, (allocated + 1) * sizeof *ptr);
if (tmp == NULL) { /* validate EVERY allocation */
perror ("realloc-tmp");
break; /* don't exit, ptr still good */
}
ptr = tmp; /* assign reallocaed block to ptr */
allocated += 1; /* increment after validation */
}
ptr[used] = b; /* assing b to ptr at index */
used += 1; /* increment after assignment */
/* debug output - remove if desired */
printf ("\nadding %d, used = %d, allocated = %d\n", b, used, allocated);
}
else if (strcmp (action, "remove") == 0) { /* ditto strmcp() */
if (used > 0) /* only decrement used if > 0, and */
used -= 1; /* (no need to adjust allocation) */
/* debug output, remove if desired */
printf ("\nremove, used = %d, allocated = %d\n", used, allocated);
}
/* debug output (no need to duplicate in each if .. else ...) */
puts ("\ncurrent content");
for (int i = 0; i < used; i++)
printf ("%d\n", ptr[i]);
}
/* [optional] final realloc to resize to exact size needed for used integers */
if (used < allocated) {
void *tmp = realloc (ptr, used * sizeof *ptr);
if (tmp) /* validate reallocation */
ptr = tmp; /* only assign after validation */
else /* otherwise just warn -- but original ptr still good */
perror ("realloc-ptr-to-used");
}
printf ("\nfinal statistics and stored values\n"
" allocated : %d\n"
" used : %d\n\n"
"stored values :\n", allocated, used);
for (int i = 0; i < used; i++)
printf ("%d\n", ptr[i]);
free (ptr); /* don't forget to free allocated memory */
}
输入文件示例
让我们使用一个示例输入文件(将文件重定向到stdin 以进行程序输入),该文件会练习程序的所有部分并包含程序应忽略的垃圾文本:
$ cat dat/add_remove.txt
alligators and other garbage
add
1 is the loneliest number that you ever knew.... (Three Dog Night)
add
2
add
3
add
4
remove
add
5
remove
remove
remove
remove
remove
add
6
add
7
remove
add
8
add
9
add
10
remove
这将添加 1-10 并删除比存储在点处更多的整数,然后继续添加和删除更多整数,直到用尽所有输入。
使用/输出示例
使用上面代码中显示的调试输出,程序输出将如下所示,最后 6, 8, 9 存储在您分配的内存块中:
$ ./bin/dyn_add_remove < dat/add_remove.txt
adding 1, used = 1, allocated = 1
current content
1
adding 2, used = 2, allocated = 2
current content
1
2
adding 3, used = 3, allocated = 3
current content
1
2
3
adding 4, used = 4, allocated = 4
current content
1
2
3
4
remove, used = 3, allocated = 4
current content
1
2
3
adding 5, used = 4, allocated = 4
current content
1
2
3
5
remove, used = 3, allocated = 4
current content
1
2
3
remove, used = 2, allocated = 4
current content
1
2
remove, used = 1, allocated = 4
current content
1
remove, used = 0, allocated = 4
current content
remove, used = 0, allocated = 4
current content
adding 6, used = 1, allocated = 4
current content
6
adding 7, used = 2, allocated = 4
current content
6
7
remove, used = 1, allocated = 4
current content
6
adding 8, used = 2, allocated = 4
current content
6
8
adding 9, used = 3, allocated = 4
current content
6
8
9
adding 10, used = 4, allocated = 4
current content
6
8
9
10
remove, used = 3, allocated = 4
current content
6
8
9
final statistics and stored values
allocated : 4
used : 3
stored values :
6
8
9
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/dyn_add_remove < dat/add_remove.txt
==2698== Memcheck, a memory error detector
==2698== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2698== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2698== Command: ./bin/dyn_add_remove
==2698==
current content
adding 1, used = 1, allocated = 1
current content
1
<snip>
final statistics and stored values
allocated : 4
used : 3
stored values :
6
8
9
==2698==
==2698== HEAP SUMMARY:
==2698== in use at exit: 0 bytes in 0 blocks
==2698== total heap usage: 7 allocs, 7 frees, 5,172 bytes allocated
==2698==
==2698== All heap blocks were freed -- no leaks are possible
==2698==
==2698== For counts of detected and suppressed errors, rerun with: -v
==2698== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
这里有很多微妙的细节,所以请放慢脚步,花点时间仔细阅读,了解为什么程序的每个部分都按原来的方式构建。具体了解为什么if (used == allocated) 的检查允许您将"remove" 操作减少为检查并从used 中减去1 在else if (strcmp (action, "remove") == 0) 之后。
查看一下,如果您还有其他问题,请告诉我。
脚注:
-
malloc、calloc 和 realloc 已从移动程序中断(使用 brk)更改为通过 memmap() 提供分配,这减少了分配惩罚,但并未完全消除它。平衡已分配块的增长与重新分配调用次数的有效分配方案仍然是一种很好的做法。