正确释放内存

时间:2020-07-29 22:13:02

标签: c dynamic-memory-allocation

我只想知道我分配给传递给readItem()的指针的内存在free中是否正确main() d,如果不正确,应该怎么做?我做对了。

感谢您的帮助,祝您生活愉快!

typedef struct Item
{
    char* itemName;
    int quantity;
    float price;
    float amount;
} Item;

void readItem(Item* ptr);

int main(int argc, char* argv[])
{
    Item sample;
    Item* p_sample = &sample;

    readItem(p_sample);
    printItem(p_sample);

    free(p_sample->itemName);
}

void readItem(Item* ptr)
{
    int size;
    printf("\n Specify the amount of letters of the product name: ");
    scanf("%d", &size);

    ptr->itemName = (char*) malloc (size * sizeof(char));

    if (ptr->itemName != NULL)
    {
        printf("\n Specify the name of the product (MAX %d letters): ", size);
        scanf("%s", ptr->itemName);

        printf("\n How many products do the company have? ");
        scanf("%d", &(ptr->quantity));

        printf("\n How expensive is the product? ");
        scanf ("%f", &(ptr->price));

        ptr->amount = (ptr->price)*(ptr->quantity);

    }
    else {
        exit(-1);
    }
}

1 个答案:

答案 0 :(得分:0)

除了上面的注释中包含的信息外,最大的问题是分配的内存字节太少。为字符串分配存储空间时,必须分配length + 1个字节,以便为 nul-termination 字符提供空间。在 1 以上size,您只为每个字符分配了足够的存储空间,而没有为 nul-termination 字符分配空间。您必须分配size + 1个字节。

此外,您必须通过检查返回值来验证每个用户输入。如果您无法检查退货,则如果用户不小心为任何数字输入输入了错误的字符而不是数字,则邀请未定义行为。要在使用scanf()时验证每个输入,您必须检查返回值是否等于指定的转换次数,例如

void readItem (Item *ptr)
{
    int size;
    
    printf ("\nSpecify the amount of letters of the product name: ");
    if (scanf("%d", &size) != 1) {              /* validate every user input */
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }
    ...

这适用于所有输入。您还应该考虑将readItem的返回类型从void更改为可以指示所有输入成功或失败的内容。 (一个简单的int并返回10即可)。这样,您可以选择处理错误并恢复而不是退出,而不用在输入或匹配失败时调用exit

此外,在用scanf()输入之后,您应该清空stdin中剩余的所有字符,直到遇到'\n'EOF。这是使用scanf()进行用户输入 1 的主要陷阱之一。这也是为什么建议使用面向行的功能(例如fgets()getline())来读取用户输入的原因。为了在使用scanf()时处理这种情况,可以在每次输入空stdin后调用一个简单的辅助函数,例如

/* function to input stdin to end of line or EOF */
void empty_stdin (void)
{
    int c = getchar();
    
    while (c != '\n' && c != EOF)
        c = getchar();
}

现在,您可以确保stdin中没有多余的字符,这些多余的字符会导致您的下一个输入失败。完成size的输入后,您可以这样做:

    printf ("\nSpecify the amount of letters of the product name: ");
    if (scanf("%d", &size) != 1) {              /* validate every user input */
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }
    empty_stdin();              /* empty all extraneous characters from stdin */
    ...

注意:通常,您不要求用户输入下一个输入的字符数,而是使用足够大的临时缓冲区存储输入并调用{{1} },以获取字符数)

strlen()包含字符数(在您的情况下为字符串的长度)的情况下,您必须分配size个字节,以便为存储 nul-terminate 提供空间。 em>字符(size + 1-或只是普通的'\0')。您将需要:

0

注意:发生故障时, ... ptr->itemName = malloc (size + 1); /* you must allocate +1 chars for '\0' */ if (ptr->itemName == NULL) { /* validate every allocation */ perror ("malloc-ptr->itemName"); exit (EXIT_FAILURE); } ... 设置了malloc,允许您使用errno输出错误)

完成您的perror()函数,您会这样做:

readItem()

您可能要解决的另一个问题是在指针值的声明中放置 ... printf ("\nSpecify the name of the product (MAX %d letters): ", size); if (scanf ("%s", ptr->itemName) != 1) { fputs ("error: read error ptr->itemName\n", stderr); exit (EXIT_FAILURE); } empty_stdin(); /* empty all extraneous characters from stdin */ printf ("\nHow many products do the company have? "); if (scanf ("%d", &ptr->quantity) != 1) { fputs ("error: invalid integer input.\n", stderr); exit (EXIT_FAILURE); } empty_stdin(); /* empty all extraneous characters from stdin */ printf ("\nHow expensive is the product? "); if (scanf ("%f", &(ptr->price)) != 1) { fputs ("error: invalid float input.\n", stderr); exit (EXIT_FAILURE); } empty_stdin(); /* empty all extraneous characters from stdin */ ptr->amount = ptr->price * ptr->quantity; } 。通常,'*'与变量一起使用,而不是类型。为什么?

'*'

不会声明三个指向int* a, b, c; 的指针,而是声明一个整数指针和两个整数。将int放在变量中可以使内容更清楚,例如

'*'

现在完全放在一起并编写一个简短的int *a, b, c; 函数,您可以做到:

printItem()

使用/输出示例

#include <stdio.h>
#include <stdlib.h>

typedef struct Item {
    char *itemName;
    int quantity;
    float price;
    float amount;
} Item;

/* function to input stdin to end of line or EOF */
void empty_stdin (void)
{
    int c = getchar();
    
    while (c != '\n' && c != EOF)
        c = getchar();
}

void readItem (Item *ptr)
{
    int size;
    
    printf ("\nSpecify the amount of letters of the product name: ");
    if (scanf("%d", &size) != 1) {              /* validate every user input */
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }
    empty_stdin();              /* empty all extraneous characters from stdin */

    ptr->itemName = malloc (size + 1);  /* you must allocate +1 chars for '\0' */

    if (ptr->itemName == NULL) {        /* validate every allocation */
        perror ("malloc-ptr->itemName");
        exit (EXIT_FAILURE);
    }
    
    printf ("\nSpecify the name of the product (MAX %d letters): ", size);
    if (scanf ("%s", ptr->itemName) != 1) {
        fputs ("error: read error ptr->itemName\n", stderr);
        exit (EXIT_FAILURE);
    }
    empty_stdin();              /* empty all extraneous characters from stdin */

    printf ("\nHow many products do the company have? ");
    if (scanf ("%d", &ptr->quantity) != 1) {
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }
    empty_stdin();              /* empty all extraneous characters from stdin */

    printf ("\nHow expensive is the product? ");
    if (scanf ("%f", &(ptr->price)) != 1) {
        fputs ("error: invalid float input.\n", stderr);
        exit (EXIT_FAILURE);
    }
    empty_stdin();              /* empty all extraneous characters from stdin */

    ptr->amount = ptr->price * ptr->quantity;
}

void printItem (Item *pitem)
{
    printf ("\nitemName   : %s\n"
            "  quantity : %d\n"
            "  price    : %.2f\n"
            "  amount   : %.2f\n",
            pitem->itemName, pitem->quantity, pitem->price, pitem->amount);
}

int main (void) {
    
    Item sample;
    Item *p_sample = &sample;

    readItem(p_sample);
    printItem(p_sample);

    free(p_sample->itemName);
}

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,$ ./bin/readitem Specify the amount of letters of the product name: 9 Specify the name of the product (MAX 9 letters): lollypops How many products do the company have? 100 How expensive is the product? .79 itemName : lollypops quantity : 100 price : 0.79 amount : 79.00 是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

valgrind

始终确认已释放已分配的所有内存,并且没有内存错误。

使用fgets()作为用户输入

如前所述,$ valgrind ./bin/readitem ==9619== Memcheck, a memory error detector ==9619== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==9619== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==9619== Command: ./bin/readitem ==9619== Specify the amount of letters of the product name: 9 Specify the name of the product (MAX 9 letters): lollypops How many products do the company have? 100 How expensive is the product? .79 itemName : lollypops quantity : 100 price : 0.79 amount : 79.00 ==9619== ==9619== HEAP SUMMARY: ==9619== in use at exit: 0 bytes in 0 blocks ==9619== total heap usage: 3 allocs, 3 frees, 2,058 bytes allocated ==9619== ==9619== All heap blocks were freed -- no leaks are possible ==9619== ==9619== For counts of detected and suppressed errors, rerun with: -v ==9619== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 的用户输入存在许多陷阱,除非解决了所有潜在的陷阱,否则它们充其量只能导致脆弱的输入例程。使用临时缓冲区(足够大的字符数组)来使用scanf()存储用户输入,然后解析fgets()所需的任何值,以确保每次都读取完整的输入行,并且不会留下多余的字符下一个输入失败。除了sscanf()之外,它还提供了许多其他方法来解析所需的临时缓冲区信息。

使用面向行的输入函数的唯一警告是,函数读取并包含sscanf()(由用户按 Enter 生成)他们填充的缓冲区。因此,只要获得输入的长度,只需用'\n'覆盖缓冲区的末端即可修剪'\n'。最可靠的方法是使用'\0'

strcspn()的返回类型更改为readItem(),以便将成功或失败的指示返回给int,您可以执行类似以下操作:

main()

通过声明一个足以容纳任何预期用户输入(包括踩在键盘上的猫声)的常数来设置临时缓冲区的大小。不要忽略缓冲区大小。 (如果为内存有限的微控制器编程则相应地减少)

如果用户通过在Linux上按 Ctrl + d 或在Windows上按 Ctrl + z 生成手册... #include <string.h> #define MAXC 1024 /* if you need a constant, #define one (or more) */ ... ,该函数将返回EOF 。函数成功返回EOF,如果遇到任何错误,则返回0,使您可以处理1中的返回值(您可以调整是否使用main()来获取成功或失败,满足您的需求)

读取,分配输入并将其复制到0变为:

ptr->itemName

注意:,无需使用int readItem (Item *ptr) { char buf[MAXC]; /* buffer to hold each user input */ size_t len = 0; /* length of product name */ fputs ("\nProduct name: ", stdout); /* fputs all that is needed, no conversions */ if (fgets (buf, MAXC, stdin) == NULL) { /* read input into buffer and validate */ fputs ("(user canceled input)\n", stdout); /* handle error */ return EOF; /* return EOF if manual EOF generated by user */ } len = strcspn (buf, "\n"); /* get length of chars (includes '\n') */ buf[len] = 0; /* overwrite '\n' with '\0' */ ptr->itemName = malloc (len + 1); /* allocate length + 1 bytes */ if (!ptr->itemName) { /* validate allocation */ perror ("malloc-ptr->itemName"); /* output error (malloc sets errno) */ return 1; /* return failure */ } memcpy (ptr->itemName, buf, len + 1); /* copy buf to ptr->itemName */ 复制到strcpy()并让ptr->itemName再次扫描字符串的结尾因为已经在调用strcpy()中完成了此操作。知道需要复制多少字节后,您可以简单地使用strcspn()复制该字节数

读取到memcpy()int的转换与float所使用的惯用语和转换说明符基本相同,例如

sscanf()

剩下的就是:

    fputs ("Quantity    : ", stdout);               /* prompt and read quantity */
    if (!fgets (buf, MAXC, stdin)) {
        fputs ("(user canceled input)\n", stdout);
        return EOF;
    }
    if (sscanf (buf, "%d", &ptr->quantity) != 1) {  /* convert to int with sscanf */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }
    
    fputs ("Price       : ", stdout);               /* prompt and read price */
    if (!fgets (buf, MAXC, stdin)) {
        fputs ("(user canceled input)\n", stdout);
        return EOF;
    }
    if (sscanf (buf, "%f", &ptr->price) != 1) {     /* convert to float with sscanf */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }

使用更新后的 ptr->amount = ptr->price * ptr->quantity; return 0; } 将等效示例放在一起,您将拥有:

readItem()

使用/输出示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 1024       /* if you need a constant, #define one (or more) */

typedef struct Item {
    char *itemName;
    int quantity;
    float price;
    float amount;
} Item;

int readItem (Item *ptr)
{
    char buf[MAXC];     /* buffer to hold each user input */
    size_t len = 0;     /* length of product name */
    
    fputs ("\nProduct name: ", stdout);             /* fputs all that is needed, no conversions */
    if (fgets (buf, MAXC, stdin) == NULL) {         /* read input into buffer and validate */
        fputs ("(user canceled input)\n", stdout);  /* handle error */
        return EOF;                                 /* return EOF if manual EOF generated by user */
    }
    
    len = strcspn (buf, "\n");                      /* get length of chars (includes '\n') */
    buf[len] = 0;                                   /* overwrite '\n' with '\0' */
    
    ptr->itemName = malloc (len + 1);               /* allocate length + 1 bytes */
    if (!ptr->itemName) {                           /* validate allocation */
        perror ("malloc-ptr->itemName");            /* output error (malloc sets errno) */
        return 1;                                   /* return failure */
    }
    
    memcpy (ptr->itemName, buf, len + 1);           /* copy buf to ptr->itemName */
    
    fputs ("Quantity    : ", stdout);               /* prompt and read quantity */
    if (!fgets (buf, MAXC, stdin)) {
        fputs ("(user canceled input)\n", stdout);
        return EOF;
    }
    if (sscanf (buf, "%d", &ptr->quantity) != 1) {  /* convert to int with sscanf */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }
    
    fputs ("Price       : ", stdout);               /* prompt and read price */
    if (!fgets (buf, MAXC, stdin)) {
        fputs ("(user canceled input)\n", stdout);
        return EOF;
    }
    if (sscanf (buf, "%f", &ptr->price) != 1) {     /* convert to float with sscanf */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }
    
    ptr->amount = ptr->price * ptr->quantity;
    
    return 0;
}

void printItem (Item *pitem)
{
    printf ("\nitemName   : %s\n"
            "  quantity : %d\n"
            "  price    : %.2f\n"
            "  amount   : %.2f\n",
            pitem->itemName, pitem->quantity, pitem->price, pitem->amount);
}

int main (void) {
    
    Item sample = { .itemName = "" };

    if (readItem (&sample) != 0)
        exit (EXIT_FAILURE);
    
    printItem (&sample);

    free(sample.itemName);
}

它同样处理空格分隔的单词以及长度不超过$ ./bin/readitem_fgets Product name: lollypops Quantity : 100 Price : .79 itemName : lollypops quantity : 100 price : 0.79 amount : 79.00 个字符的字符串:

MAXC - 1

内存使用/错误检查

与上述相同的输出,分配的字节数相同,结果如第一个示例所示。

与我提供的评论一起,这是用户输入的基本细节,不同的考虑因素以及处理分配给字符串输入的方式。仔细研究一下,如果您还有其他问题,请告诉我。

脚注:

1。 $ ./bin/readitem_fgets Product name: "My dog has fleas and my cat has none?" (McGraw-BigHill 1938) Quantity : 10 Price : 19.99 itemName : "My dog has fleas and my cat has none?" (McGraw-BigHill 1938) quantity : 10 price : 19.99 amount : 199.90 对于新C程序员来说充满了陷阱。推荐的用户输入方法是使用面向{em> line 的功能,例如scanf()或POSIX fgets()。这样,您可以确保在匹配失败的情况下,可以读取完整的输入行,并且确保字符不会保留在输入缓冲区(getline())中。一些链接讨论了stdinC For loop skips first iteration and bogus number from loop scanfTrying to scanf hex/dec/oct values to check if they're equal to user inputHow do I limit the input of scanf to integers and floats(numbers in general)

的正确使用