【问题标题】:c pointer, how to free it/them into a functionc指针,如何将它/它们释放到函数中
【发布时间】:2012-05-05 17:15:53
【问题描述】:

这是我的代码:

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

void getinfo(unsigned int a, unsigned int b, char **pStr);

int main(){
    unsigned int len_max = 8;
    unsigned int current_size = 0;
    current_size = len_max;
    char *host, *user;
    char *pStr = malloc(len_max);
    if(pStr == NULL){
        perror("\nMemory allocation\n");
        return EXIT_FAILURE;
    }
    printf("Inserisci hostname: ");
    getinfo(len_max, current_size, &pStr);
    if((host=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(host, pStr, strlen(pStr)+1);
    printf("Inserisci username: ");
    getinfo(len_max, current_size, &pStr);
    if((user=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(user, pStr, strlen(pStr)+1);
    printf("\nHostname: %s\nUsername: %s\n", host, user);
    free(pStr);
    free(host);
    free(user);
    return EXIT_SUCCESS;
}

void getinfo(unsigned int a, unsigned int b, char **pStr){
    unsigned int i = 0;
    int c = EOF;
    while((c = getchar()) != '\n'){
        (*pStr)[i++] = (char)c;
        if(i == b){
            b = i+a;
            if((*pStr = realloc(*pStr, b)) == NULL){
                perror("\nMemory allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    (*pStr)[i]='\0';
}

问题是如果 realloc 失败我必须退出(因为我无法分配内存)。但在退出之前,需要释放所有使用过的指针。
问题是,如果函数第一次失败,则只有 1 个指针必须被释放 (pStr)。
但如果第二次失败,则必须释放 2 个指针(pstr 和用户)。
我该如何解决?

【问题讨论】:

  • 退出前不必释放内存,操作系统会为你做的
  • 我已经在你之前的问题中考虑过这个问题。您的代码在这方面很好,因为操作系统会简单地回收内存。
  • 在退出之前free 被认为是一种很好的做法,尽管大多数新操作系统都会为您这样做。有this相关问题
  • 为什么不将函数更改为返回值来指示函数是否有任何故障?将返回类型更改为 int 并在函数成功完成时返回 0 否则返回 1。而不是退出函数返回成功/失败。现在在代码中,检查函数的返回值,如果不为0,则适当释放资源

标签: c function pointers


【解决方案1】:

如前所述,如果你要退出,那么所有实用的现代操作系统都会在退出前释放分配的内存。并非总是如此。 AmigaDOS 的早期版本 IIRC 在重新启动之前不会自动回收分配的内存。

这是一个简单的案例。还有更复杂的情况,比如你将一个文件解析到内存中,第 579th 个内存分配失败,你想释放之前的 578 个内存分配,让用户可以重试。

在这种情况下,您必须记录每个相关的内存分配(这本身可能需要一些内存分配 - 尽管如果您正在解析文件,您可能有一个包含完整描述的主结构)然后释放所有分配的数据。

在您的示例中,如果这不是 main() 函数并且您没有因内存分配错误而中止,那么您需要确保在退出函数时释放三个分配的指针。标准技巧包括:

  1. 将指针初始化为 0,以便可靠地释放它们:

    char *host = 0;
    char *user = 0;
    
  2. 使用realloc()时,不要将结果赋给作为第一个参数传递的表达式:

    不要这样做:

    ptr = realloc(ptr, newsize);
    

    如果(当)ptr 是对已分配内存的唯一引用并且重新分配失败,则您只是泄漏了内存;没有办法释放仍然分配给你的内存。

    用途:

    void *newptr = realloc(oldptr, newsize);
    if (newptr == 0)
        ...recover from memory allocation failure
    oldptr = newptr;
    

    更简单版本的问题在于您刚刚丢弃了对已分配内存的唯一引用。 (请注意,您的代码属于危险/泄漏模式)。

  3. 请注意,几乎每个获取资源的函数都必须在返回之前释放所获取的资源,或者让程序的其他部分可以使用该资源,以便其他部分在使用完资源后可以释放该资源。

    “使可用”操作可能会将获取的资源(将其视为内存,但它可能是文件描述符、目录流或大量其他已分配资源中的任何一个)返回给调用函数,或者将其存储在传递给当前函数的结构中,或将其复制到全局或(文件)静态变量,甚至将其存储在(函数)静态变量中,因此如果再次调用该函数,它有一些可用资源进入时。

【讨论】:

  • 哇! :O 非常感谢您的回答!所以我必须更改我的代码,因为此时它处于“危险状态”,对吧?
  • 鉴于您的代码当前在realloc() 失败时立即退出,因此更改以避免ptr = realloc(ptr, newsize); 反模式并不重要。但是,如果您要尝试从错误中恢复,那么避免反模式至关重要。你需要意识到这个问题。总的来说,做出改变是为了更好。但是如果你在失败后继续退出,这并不重要。
  • 嗯,好吧,我不明白这部分,但是......我必须做些什么才能“从内存分配失败中恢复”?什么意思?
  • 在这个简单的程序中,如果内存分配失败,您无能为力;错误退出是一个完全合理的策略。但是,我在答案中提到了一个示例,您可能需要更好的策略(“读取文件和第 579 个分配失败”部分)。例如,你的程序可能被调用转换了20个文件,而第三个文件恰好太大而无法转换;您应该放弃它的转换并避免转换任何其他文件,还是应该释放用于第三个文件的内存并继续处理其余文件? (续)
  • (继续) 这两个答案都是可能的。总的来说,释放内存并继续下一个文件更好,但这并不是一个明确的决定。我的许多程序都使用“弃船”策略(通过一组例程emalloc()erealloc()ecalloc()efree(),它们从不返回空指针);他们避免在分配失败时进行恢复。但它们也不会像筛子一样泄漏。在处理每个文件结束时,分配用于处理该文件的内存全部被释放并可用于处理下一个文件。这是一个判断电话,而不是一个简单的电话。
【解决方案2】:

正如一些人所指出的,现代操作系统在退出时会回收内存。但是,无论如何,释放资源被认为是最佳实践,因为这使调试更容易。例如,如果您尝试查找泄漏并使用像 valgrind 这样的工具,那么您没有正确释放的所有内存(即使通过程序逻辑,这无关紧要)都将显示为泄漏。众所周知,周围有一些大型 API 不这样做,并且它们使使用它们的应用程序中的跟踪泄漏成为一场噩梦。

此外,在某些特殊环境中,自己清理可能很重要。所以,现在就养成是一个好习惯。

您偶尔会看到的一种清理技术(例如,在 linux 内核中)是我认为的“保释和释放”模式。这是goto 仍然被认为可以接受的少数(也许:唯一)上下文之一。这取决于您是否能够以与分配资源相反的顺序释放资源。通常这是在单个函数的上下文中,在本例中为main()

#include <stdlib.h>

int main(void) {
    int exit_status = EXIT_FAILURE;

    char *s1, *s2, *s3;

    s1 = malloc(100);
    if (!s1) return EXIT_FAILURE;

    s2 = malloc(100);
    if (!s2) goto endB;

    s3 = malloc(100);
    if (!s3) goto endA;

    exit_status = EXIT_SUCCESS;

    /* do whatever */

    free(s3);
endA:
    free(s2);
endB:
    free(s1);
    return exit_status;
}            

解释一下:如果分配s1 失败,我们就返回——没有什么需要清理的。但是如果分配s2 失败,我们转到endB,释放s1。如果分配s3 失败,我们转到endA,这将释放s2s1。最后,如果一切都成功了,我们就做任何事情,然后,所有三个指针都将被释放。如果这是一个普通函数,我们可能会返回一个指针,所以在“end”位之前会有一个单独的返回,它会以“return null”结束。

注意:请不要以此为许可随意使用goto

【讨论】:

  • goto 没有错——您可能会争辩说,编写正确的 C 语言是必要的! :-) 在某种程度上,C++ 就是带有自动魔法gotos 的 C,称为“析构函数”。
【解决方案3】:

是的,如果这是整个程序,您不必释放任何东西。

释放内存的全部原因是它可以在程序稍后的某个地方重用。即使您的程序从此时开始继续,您也只分配了少量字节。分配它们并永久保留它们是可以的。

事实上,你甚至不需要做你正在做的算术来计算出精确大小的 malloc,你可以说哦,用户名和主机名永远不会超过 30 个字符,只是为了确保我'将分配 256 个字符块。哦,等等,你的最大值是 8 个字符,无论如何。或者甚至只是制作一个 256 个字符或 8 个字符长的全局缓冲区。然后确保您的 strncpy()s 永远不会超过 len_max,否则您将面临缓冲区溢出黑客攻击的风险。

同时 getinfo() 看起来很痛苦。试试 fgets(mybuffer, len_max, stdin)。

最后我检查了一下,可执行文件甚至懒得在最后“释放”所有未释放的块,它只是走开了。 VM系统将所有使用的内存(包括堆栈和程序代码)返回给操作系统,进程蒸发,结束。 malloc()ed 块只是该内存上的一种字节模式,它都被遗忘了。

【讨论】:

  • 是的,你是对的。事实上,我最初认为这没有必要,但我想学习使用 realloc,所以我更喜欢这样做 :)
【解决方案4】:

这更像是一个通用的 C 语言建议,而不是一个具体的答案,但是评论太长了。

在存在动态资源管理的情况下编写 C 的常用方法是 goto 合适的标签,然后是相关的释放调用:

int f(int n)
{
    void * p1, * p2, * p3;
    int result = -1;

    p1 = malloc(n);

    if (!p1) goto END0;
    if (n % 2) goto END1;

    p2 = malloc(3 * n);

    if (!p2) goto END1;
    if (n % 7 == 0) goto END2;

    p3 = malloc(7 * n + 8);

    if (!p3) goto END2;

    if (do_useful_stuff(p1, p2, p3) == -1) goto END3;
    result = compute_more_stuff(p1, p2, p3);

END3:
    free(p3);
END2:
    free(p2);
END1:
    free(p1);
END0:
    return result;
}

另一种方法是将您的代码拆分为非常小的函数,一次只做一件事并以特别的方式处理资源分配:

int small_function(void ** p)
{
    void * q = malloc(13);

    if (some_init(&q) == -1)
    {
        free(q);    // ad-hoc exit management
        return -1;
    }

    int n = complex_computation(q);
    free(q);
    return n;
}

【讨论】:

  • 添加了一个小的错字更正。 n % 2 等是有原因的还是只是一个示例?
  • @another.anon.coward:只是一些人为控制流的愚蠢示例!关键是该函数可能会根据运行时数据以多种方式退出。 (我在 goto 中有一个错误,现在已修复。)
【解决方案5】:

您不必在退出前释放动态分配的内存。操作系统会将所有内存提供给下一个进程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-14
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2022-06-14
    • 1970-01-01
    • 2020-11-22
    相关资源
    最近更新 更多