在C语言开发中,函数通过返回值向调用者传递执行结果或状态,但很多初学者写代码时直接忽略返回值,导致程序在异常情况下行为不可控。判断返回值的核心在于先弄清楚被调函数的返回约定,再针对性地做条件分支处理。

一、明确函数的返回约定
C语言标准库和第三方库对于函数返回值并没有统一规范,因此判断之前必须查文档或头文件注释。常见的约定有三种:第一种是返回0表示成功,非0表示错误,例如POSIX风格的很多系统调用;第二种是返回有效指针表示成功,返回NULL表示失败,例如内存分配或文件打开函数;第三种是返回大于等于0的值表示正常结果,返回负值表示错误码,例如read、write等输入输出函数。
如果不清楚约定就盲目写if(ret),很可能把合法的结果当成错误。比如strcmp返回0表示两字符串相等,若写成if(strcmp(a,b))就会把相等情况误判为不相等。所以第一步永远是确认该函数“什么叫成功,什么叫失败”。
二、基础判断方式
最直观的做法是用关系或相等运算符将返回值与预期常量比较。以下代码展示如何安全打开文件并判断:
#include <stdio.h>
int main(void) {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("文件打开失败n");
return 1;
}
printf("文件打开成功n");
fclose(fp);
return 0;
}
上例中fopen失败返回NULL,因此用相等判断最为清晰。对于返回0成功的函数,则应使用if (ret != 0)捕获异常。这种显式比较比单纯写if (ret)更容易让阅读者理解意图。
有些函数返回负值表示错误,这时可以和0比较:
#include <unistd.h>
#include <stdio.h>
int main(void) {
char buf[64];
ssize_t n = read(0, buf, sizeof(buf));
if (n < 0) {
printf("读取失败n");
return 1;
}
printf("读取了 %zd 字节n", n);
return 0;
}
这段代码中read返回读取字节数,负数代表错误。将其与0比较就能准确判断,而不依赖“非0即真”的模糊逻辑。
三、配合errno定位错误原因
很多C库函数在返回错误值时,会同时设置全局变量errno。仅判断返回值只能知道“失败了”,结合errno才能知道“为什么失败”。需要包含errno.h并使用perror或strerror输出信息。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
printf("打开失败,错误号:%d,信息:%sn", errno, strerror(errno));
return 1;
}
fclose(fp);
return 0;
}
使用errno时要注意,它只在函数出错后被设置,成功时不会被清零,所以必须在确认返回值错误后立即读取,不要穿插可能改变errno的其他调用。perror函数会自动把错误信息接到给定字符串后并打印,非常适合快速调试。
在写库函数时,若自己设计接口,也建议采用类似机制:返回约定状态值,并在出错时通过参数指针或全局量给出原因,这样调用方才能正确判断和处理。
四、用宏和封装减少重复判断
当项目中大量调用同一类函数时,重复的if判断会让代码冗长。可以通过宏或静态封装函数统一处理。例如封装一个必须成功打开文件的函数:
#include <stdio.h>
#include <stdlib.h>
FILE *open_or_die(const char *path, const char *mode) {
FILE *fp = fopen(path, mode);
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
return fp;
}
这样业务代码里直接调用open_or_die,不必每次都判断,但代价是程序遇到错误直接退出,不适合需要恢复的场景。若需要恢复,可以定义CHECK宏把判断逻辑集中:
#include <stdio.h>
#define CHECK_RET(ret, msg)
do {
if ((ret) < 0) {
printf("错误:%sn", msg);
return -1;
}
} while (0)
int demo(void) {
int ret = 0;
CHECK_RET(ret, "示例错误");
return 0;
}
宏里的do while(0)是常见技巧,能保证在if后面加分号时不会破坏语法结构。通过这类封装,既能统一判断标准,也降低遗漏返回值检查的概率。
五、常见误区与建议
一个典型误区是把void函数当作有返回值函数来判,或者反过来忽略本应判断的非void函数。另一个误区是认为返回值非0一定成功,比如有些Windows API返回非0表示成功,若套用Linux习惯就会写反逻辑。建议每调用一个不熟悉的函数,先确认其返回类型和含义,再写判断。
对于资源型函数(分配内存、打开句柄),判断返回值后务必在错误分支释放已得资源或退出,防止泄漏。养成“调用即判断”的习惯,能让C程序在边界输入和异常环境下依然稳定运行。