【问题标题】:How do I say ptr+1=(int*) malloc(1 * sizeof(int)); correctly?怎么说 ptr+1=(int*) malloc(1 * sizeof(int));正确吗?
【发布时间】:2021-05-18 15:45:02
【问题描述】:

我是 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;
}  

【问题讨论】:

  • IDK,你ptr+1=(int*) malloc(1 * sizeof(int));的目标是什么?
  • 旁白:放下演员表。 int *ptr = malloc(1 * sizeof(int)); 没问题。 int *ptr = malloc(sizeof *ptr); 更好。
  • 这很难解释,我正在尝试做“堆栈和队列”,我想用 ptr+1 分配“内存的下一部分”,我知道如何解释它。跨度>
  • ptr 指向分配在堆栈上的单个 int。请勿尝试将 anything 分配给 ptr + 1,因为这不是已分配给您对其执行任何操作的空间。
  • 我想你想要的是 realloc。 Google for realloc stack,例如:stackoverflow.com/questions/41918690/using-realloc-on-a-stack

标签: c pointers memory-management malloc


【解决方案1】:

这是一个基本而重要的问题。您不仅需要知道如何处理动态分配,还需要知道如何以结构化的方式处理它。首先,您需要两个计数器。一个计数器跟踪您分配的整数数量,第二个计数器跟踪使用的整数数量。当你"remove"一个整数时,你不需要重新分配,只有在添加和used == allocated时才需要。否则,如果used &lt; 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 &gt; 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 中减去1else if (strcmp (action, "remove") == 0) 之后。

查看一下,如果您还有其他问题,请告诉我。

脚注:

  1. malloccallocrealloc 已从移动程序中断(使用 brk)更改为通过 memmap() 提供分配,这减少了分配惩罚,但并未完全消除它。平衡已分配块的增长与重新分配调用次数的有效分配方案仍然是一种很好的做法。

【讨论】:

    【解决方案2】:

    您提供的代码中存在多个问题。 前任。在您的 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
    

    【讨论】:

    • malloc() 的结果不必在 C 中强制转换;事实上,不这样做是惯用的。最好使用表达式而不是显式类型作为malloc() 的参数:int *ptr = malloc(sizeof *ptr); 这更容易阅读,更少输入,更不容易出错,并且在代码更改时更易于维护。而malloc() 可能会失败;您必须检查返回值以确保 malloc() 成功,否则将面临未定义行为的风险。
    【解决方案3】:

    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。在realloc之后,您不能使用array 的旧值。
    • 如果realloc(或malloc)失败,此程序的行为未定义。
    • 每次将数组增大和缩小 1 是非常低效的。使用第三个变量来跟踪数组的总容量(与长度不同),分配比您需要的更多的内存并按大块增长它更常见、更快。
    • Don't cast the return value of malloc and friends

    【讨论】:

    • @IDK 请为好的答案投票,如果其中一个解决了您的问题,请单击复选标记将其标记为已接受。 (如果有多个答案回答了这个问题,你应该选择你最喜欢的一个,或者对你最有帮助的一个;不要因为接受我的压力而感到压力)。将答案标记为接受可以让其他人知道您不再在寻找答案。谢谢,欢迎来到 Stack Overflow!
    猜你喜欢
    • 2020-09-07
    • 2022-01-15
    • 2013-02-19
    • 1970-01-01
    • 2014-08-13
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多