问题描述
我是 C 初学者,刚开始学习内存管理和指针。我知道两者的基础知识,所以我先这样做:
int *ptr;
ptr=(int*) malloc(1 * sizeof(int));
然后我想这样做:
ptr+1=(int*) malloc(1 * sizeof(int));
但我不断收到此错误:[Error] lvalue required as left operand of assignment
。我如何正确地写上一行?先感谢您。这是我的代码:
#include <stdio.h>
#include <stdlib.h>
int *ptr,number_of_elems=1;
int i;
int b;
int main()
{
ptr=(int*) malloc(1 * sizeof(int));
int a=1;
ptr=&a;
char action[6]; /*add/remove*/
while(1){
scanf("%s",&action);
if(action == "add"){
number_of_elems+=1;
ptr+number_of_elems -1=(int*) malloc(1 * sizeof(int));
scanf("%d",&b);
ptr+number_of_elems -1=&b;
for(int i=0;i<number_of_elems;i++){
printf("%d\n",ptr+i);
}
}
else if(action=="remove"){
number_of_elems-=1;
free(ptr+number_of_elems-1);
for(int i=0;i<number_of_elems;i++){
printf("%d\n",ptr+i);
}
}
}
}
我知道如何在没有内存管理的情况下做到这一点。这是我尝试使用 malloc
转换的工作代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int array[50];
int newElem;
int lastElem=0;
array[0]=1;
char action; /*a/r a stand for add and r for remove*/
while(1){
scanf("%c",&action);
if(action == 'a'){
lastElem+=1;
scanf("%d",&newElem);
array[lastElem]=newElem;
for(int i=0;i<lastElem+1;i++){
printf("%d",array[i]);
printf("%c",' ');
}
}
if(action=='r'){
lastElem-=1;
free(&array[lastElem]);
for(int i=0;i<lastElem+1;i++){
printf("%d",array[i]);
printf("%c",' ');
}
}
}
return 0;
}
解决方法
这是一个基本而重要的问题。您不仅需要知道如何处理动态分配,还需要知道如何以结构化的方式处理它。首先,您需要两个计数器。一个计数器跟踪您分配的整数数量,第二个计数器跟踪使用的整数数量。当您 "remove"
一个整数时,您不需要重新分配,只有在添加和 used == allocated
时才需要重新分配。否则,如果 used < allocated
,则无需为 "add"
之后的下一个整数分配。
理想情况下,您希望分配一些预期数量的整数,并且仅在初始分配的内存块中的空间不足时才重新分配(通常每次需要重新分配时将当前分配的块的大小加倍)以最小化数量调用 realloc()
1 的成本相对较高。它确实增加了一点点复杂性。对于学习练习,为每个新的 int
分配是可以的。
在查看分配方案之前的另一个注意事项是使用 scanf()
进行输入。这导致了一个非常脆弱的输入方案。输入中的一个杂散字符将导致 匹配失败,其中坏字符在 stdin
中未被读取,从而破坏您的下一个(如果您依赖输入顺序 - 您的其余部分)输入。使用 面向行 输入函数(如 fgets()
或 POSIX getline()
)读取输入要好得多,这样每次读取都会消耗整行输入。然后,您可以使用 fgets()
从由 sscanf()
填充的缓冲区(字符数组)中解析您需要的值。这样,无论 sscanf()
的转换成功还是失败,stdin
中都不会有任何未读内容。
另外,避免使用全局变量。相反,在需要它们的范围内声明变量,然后将任何需要的信息作为参数传递给函数,而不是依赖全局变量。 (它们有其适当的位置——但是当你学习 C 时,你不会找到它们,除非你在微控制器上编程)
现在对于您的分配方案,将 number_of_elems
的名称更改为 allocated
以跟踪分配给的整数数量并添加 used
以跟踪使用的 int
数量在您分配的块中,您的方案将是:
- 初始化
ptr = NULL;
以开始, - 在读取
"add"
并使用strcmp()
进行确认后(您将 C 中的字符串相等性与strcmp()
进行比较,而不是==
),您将读取并验证后面的整数,然后 - 比较
if (used == allocated)
以确定是否需要分配新整数,如果需要 - 对所有分配使用
realloc()
(当指针为realloc()
时,malloc()
的行为类似于NULL
), - 在您验证分配后增加
allocated += 1;
并将整数值存储到新的内存块后,您增加used += 1;
, - 如果选择了
"remove"
,那么您只需要验证used > 0
,如果是这样,只需减少used
,此时无需调整分配——即仅在"add"
时发生。 - [可选] 在您阅读并完成
"add"
/"remove"
循环后,您可以制作最后一个realloc()
以将分配的内存块大小调整为存储 {{ 1}} 个整数。
那么,你会如何实现呢?让我们从初始化所有变量的 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,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()
失败,您不会用 {{ 覆盖您的指针地址1}} 由于丢失了指针持有的地址(现在不能传递给 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,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"
。在 1
块之后,您可以输出使用的已分配块的当前内容:
used
现在,可选在您的读取循环完成后,您可以将分配的大小调整为仅容纳整数 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,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,/* number of integers used */
*ptr = NULL;
while (1) {
int b = 0; /* declare variables in scope needed */
/* control read-loop with function return,stderr);
return 1;
}
/* realloc() behaves as malloc() if ptr is NULL,allocated);
}
else if (strcmp (action,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
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2 个责任:(1) 始终保留一个指向起始地址的指针内存块,(2) 可以在不再需要时释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或超出/超出分配块的范围进行写入,尝试读取或基于未初始化值的条件跳转,最后,以确认您释放了所有分配的内存。
对于 Linux 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,allocated = 4
current content
1
2
3
adding 5,allocated = 4
current content
1
2
3
5
remove,allocated = 4
current content
1
2
3
remove,allocated = 4
current content
1
2
remove,allocated = 4
current content
1
remove,used = 0,allocated = 4
current content
remove,allocated = 4
current content
adding 6,allocated = 4
current content
6
adding 7,allocated = 4
current content
6
7
remove,allocated = 4
current content
6
adding 8,allocated = 4
current content
6
8
adding 9,allocated = 4
current content
6
8
9
adding 10,allocated = 4
current content
6
8
9
10
remove,allocated = 4
current content
6
8
9
final statistics and stored values
allocated : 4
used : 3
stored values :
6
8
9
始终确认您已释放分配的所有内存,并且没有内存错误。
这里有很多微妙的细节,所以放慢速度,花点时间仔细研究一下,了解为什么程序的每个部分都按照原来的方式构建。具体理解为什么 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,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"
中减去 1
。
检查一下,如果您还有其他问题,请告诉我。
脚注:
-
used
、else if (strcmp (action,"remove") == 0)
和malloc
已从移动程序中断(使用calloc
)改为通过realloc
提供分配,这减少了分配惩罚但并没有完全消除它。平衡分配块的增长与重新分配调用的数量的有效分配方案仍然是一种很好的做法。
您提供的代码存在多个问题。 前任。在 main() 中,您为 ptr 分配了动态内存块。将整数“a”的地址分配给“ptr”,而无需释放先前的内存分配,这会在您的程序中造成内存泄漏。
以下程序可能会消除您的疑虑。
#include <stdio.h>
#include <stdlib.h>
int main(){
// Create dynamic memory block and assign it to ptr integer pointer
int *ptr=(int *)malloc(1 *sizeof(int));
// Adding value in dynamic memory block
*ptr=5;
// Printing address ptr,address of dynamic memory block and its value
printf("Pointer Address=%p,Memory Address=%p,Value=%d\n",&ptr,ptr,*ptr);
// Left side value is not varaible. It is fix so it gives lvalue error,like assigning 1=2.
// ptr+1=(int *)malloc(1 *sizeof(int));
// below ptr+1 indicate memory location which not allocated for this program
printf("Unallocated Memory Address=%p\n",ptr+1);
// Release memory which created as dynamic memory block
free(ptr);
return 0;
}
输出:
Pointer Address=0x7ffc1e9a82c0,Memory Address=0x55a0d4a9e260,Value=5
Unallocated Memory Address=0x55a0d4a9e264
,
ptr+1=(int*) malloc(1 * sizeof(int));
这不是增加 ptr
持有的分配大小的方式。在第一个 malloc
之后,ptr
包含 1-int
大小的内存块的地址。 ptr + 1
是这个块的最后一个地址。第二次调用 malloc
将返回另一个 1-int
大小的内存块的地址。但是你不能通过将一个分配给另一个来使它们相邻,就像你不能写 2 + 1 = 4;
并打破宇宙一样。
相反,您必须请求将 ptr
持有的块调整大小。您可以使用 realloc
函数执行此操作。这是一个程序,它执行我认为您正在尝试执行的操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int length = 1; // initial length of the array
int *array = malloc(length * sizeof *array);
array[0] = 1;
while (1) {
char action;
scanf("%c",&action);
if (action == 'a') {
int newElem;
scanf("%d",&newElem);
// grow the array by 1
array = realloc(array,(length + 1) * sizeof *array);
array[length] = newElem;
length += 1;
for (int i = 0; i < length; i++) {
printf("%d ",array[i]);
}
}
if (action == 'r') {
// shrink the array by 1
array = realloc(array,(length - 1) * sizeof *array);
length -= 1;
for (int i = 0; i < length; i++) {
printf("%d ",array[i]);
}
}
}
return 0;
}
注意:
-
realloc
可以返回一个与传递给它的指针不同的 指针。这意味着没有足够的堆空间来调整块的大小,因此数据被复制到一个新块,旧块已被释放。这就是您应该将结果分配回array
的原因。array
后不得使用旧的realloc
值。 - 如果
realloc
(或malloc
)失败,此程序的行为未定义。 - 每次将数组扩大和缩小 1 是非常低效的。分配比您需要的更多的内存并按大块增长,使用第三个变量来跟踪数组的总容量(与长度不同)更为常见且速度更快。
- Don't cast the return value of
malloc
and friends