如何优雅地处理malloc错误,而不需要在每个malloc调用后都检查是否返回了NULL?

7

在我的代码中,几乎每个函数都有一个或多个malloc调用,每次我都需要做类似以下的事情:

char *ptr = (char *)malloc(sizeof(char) * some_int);
if (ptr == NULL) {
    fprintf(stderr, "failed to allocate memory.\n");
    return -1;
}

这将增加四行代码,如果我每次使用malloc后都要添加它们,我的代码长度将大大增加..所以有没有一种优雅的方法来解决这个问题呢?

非常感谢!


如果你的代码长度会增加很多,那么你应该考虑为什么要频繁调用 malloc。这可能表明你试图将其他语言的习惯用法翻译成 C 语言,而不是正确地使用 C 语言... - R.. GitHub STOP HELPING ICE
3
请注意,在C语言中,不需要将malloc的返回值强制转换类型,这样做可能会掩盖编译器本来可以发现的错误。 - pmg
@pmg,你是在谈论(char *)对吧?这种方法可以遮盖哪些错误? - cokedude
1
@cokedude:如果您没有包含<stdlib.h>,就会发生错误隐藏,从而在没有原型的情况下使用函数。编译器(错误地)假定malloc()返回类型为int的值,并且没有强制转换时,在赋值时会因为int*int不兼容而报错。通过强制转换,您可以迫使编译器保持沉默...隐藏正确的#include遗漏。 - pmg
8个回答

5

当所有内存都被占用时,试图继续操作可能毫无意义。不如放弃:

char* allocCharBuffer(size_t numberOfChars) 
{
    char *ptr = (char *)malloc(sizeof(char) * numberOfChars);
    if (ptr == NULL) {
        fprintf(stderr, "failed to allocate memory.\n");
        exit(-1);
    }
    return ptr;
}

1
有没有一些清理程序?删除临时文件等等。 - CMCDragonkai
你可以使用 atexit() - Alexander

3
您可以使用宏。这比将此代码分组为函数更便宜,因为宏不具有函数调用所需的开销。宏由编译器的预处理阶段扩展,并且可以通过gcc中的“-E”选项进行验证。 现在假设我们有func1(),func2(),func3().
#define MY_MALLOC(_ptr,_count, _lbl) \
do { \
 if (NULL == (ptr = malloc(sizeof(char) * _count))) { \
    fprintf(stderr, "Failed to allocate memory.\n"); \
    goto _lbl; \
 } \
} while(0)

func1() {  
 char *ptr;
 MY_MALLOC(ptr,10,Error);
 ....
 ...
 return (0);
Error:
 return (1);
}


func2() {  
 char *ptr;
 MY_MALLOC(ptr,10,Error);
 ....
 ...
 return (0);
Error:
 return (1);
}


func3() {  
 char *ptr;
 MY_MALLOC(ptr,10,Error);
 ....
 ...
 return (0);
Error:
 return (1);
}

#undef MY_MALLOC

3
抱歉,在C语言中你无法解决这个问题。除非... 惊喜地说...将所有内容封装在宏中,这将自动执行if检查并允许您每次编写自定义错误处理代码。就是这样。
严肃点说,C语言不应该提供这种便利。当然,如果您不介意立即退出程序,您可以将其包装在一个函数中,如果分配失败则执行exit -- 但这并不是通用的解决方案。

4
可以将其包装在一个函数中而不是宏中。 - user142019
2
@WTP:这个函数的问题在于它可以检测到条件,但实际上无法真正处理它。至少不是没有你在每个调用点都写上4行代码那么麻烦。至少就我所知道的情况而言。 - Jon

1

当你没有真正的错误处理(除了打印一些东西并退出)时,简单而已经建立的解决方案是定义一个函数 safe_malloc 来包含检查。(编辑:或者当然也可以使用宏。任何方式都可以。)


0

C运行时应该清理任何资源,包括打开的文件、缓冲区和分配的数据。即便如此,我仍然喜欢使用int atexit( void(*)(void)),它会在正常退出时调用已注册的函数。如果atexit返回非零值,意味着您的函数未被注册,则立即退出。

#include <stdlib.h>
void register_cleanup ( void ( *cleaner )( void ))
{
    if ( atexit ( cleaner ))
    {
        fprintf ( stderr, "Error, unable to register cleanup: %s\n",
                strerror ( errno )) ;
        exit ( EXIT_FAILURE ) ;
    }
}

如果 malloc 分配内存失败,则退出。

#include <stdlib.h>
void *malloc_or_die ( size_t size )
{
    void *dataOut = malloc ( size ) ;
    if ( !dataOut )
    {
        fprintf ( stderr, "Error, malloc: %s\n", strerror ( errno )) ;
        exit ( EXIT_FAILURE ) ;
    }
    return dataOut ;
}
void main()
{
    register_cleanup ( cleaner_fnc ) ;
    ...
    void *data = malloc_or_die ( 42 ) ;
    do_stuff ( data ) ;
    return 0 ;
}

这并没有考虑在同一函数内需要释放的其他数据,或者在 atexit 函数中未处理的数据。来自 Anoop Menon 的宏可以解决这个问题。如果错误处理在每个函数中都不同,那么不使用宏抽象会更清晰。 - Funmungus

0
#define my_malloc_macro(size, ptr) do { \
    ptr = malloc(size); \
    if(!ptr) { \
        printf("malloc failed\n"); \
        return -1; \
    } \
} while(0)

0

如果您的错误条件始终如此简单(打印错误消息并返回),则可以重写以节省代码行数。

int errmsg(const char *msg, int retval) {
    fprintf(stderr, "%s\n", msg);
    return retval;
}

if ((ptr = malloc(size)) == NULL) return errmsg("failed to allocate memory.", -1);
/* ... */
free(ptr);

0

或者你可以使用 extern。

在 main.c 中定义一个函数:

 void sj_handleException(bool fatal, const char* msg, const char* libMsg){
  fprintf(stderr, msg);
  if(libMsg != NULL) fprintf(stderr, libMsg);

  if(fatal) exit(EXIT_FAILURE);    
}

任何malloc内存的文件都应该像前向声明一样添加:
extern void sj_handleException(bool fatal, const char* msg, const char* libMsg)

现在将malloc写成:

char *ptr = (char *)malloc(sizeof(char) * some_int);
if (ptr == NULL) sj_handleException(true, "failed to allocate memory.\n", NULL);

在你的代码中动态分配内存的地方和处理异常的main.c之间的链接是由链接器在幕后生成的;它将对函数的调用与函数进行映射,即使这两个存在于不同的源文件中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接