我找不到从函数内部的函数返回错误并结束程序的方法。
我当时正在考虑使用一种结构并将返回值存储在内部并以这种方式结束它,但是我不知道这是否是最佳实践
让我们说我有一个这样的程序:
int main()
{
// do stuff
importantFunction();
// do stuff
return 0;
}
在ImportantFunction()中,我正在调用其他两个函数,这些函数在进行某些Bitshifting并始终返回一个数组。现在,如果该函数之一发生错误,我想尝试返回1(或0x01,因为该函数正在返回数组指针),但是我不确定如何。
char *importantFunction()
{
//do stuff
secoundFunction();
thirdFunction();
//do stuff
return array;
}
char *secoundFunction()
{
// do stuff
if (something == x)
return array;
// do stuff
return array;
}
我只是想找到一个方法,而不必检查第一个函数是否等于某个方法,然后在int main中结束程序。 我试图避免这种情况,因为它并不总是有效:
int main()
{
// do stuff
char *pointer = importantFunction();
if (*pointer == 'something')
return 1;
if (*pointer == 'something')
return 2;
if (*pointer == 'something')
return 3;
// and so on...
// do stuff
return 0;
}
如果这是一个愚蠢的问题,我很抱歉,我不太会问问题。
答案 0 :(得分:4)
从函数返回错误的三种常见模式:
该函数是否返回int
,并带有指示成功和失败的特定值
例如,从EXIT_SUCCESS
返回EXIT_FAILURE
或main()
是C标准建议报告整个过程成功或失败的方式。 (BSD变体已尝试标准化其他一些代码;如果您的系统具有<sysexits.h>
标头,则可以使用这些标头。但是请注意,它们不是“标准”的,只是我们必须达成的最接近的协议进程可以报告错误代码。)
为错误保留一个特定的返回值,并使用全局或线程局部变量(通常为errno
)来描述错误
大多数标准C库函数都执行此操作,其中函数使用错误int
返回-1
,函数使用NULL
返回指示错误的指针。
< / p>
使用额外的参数指向错误指示器。
这种方法在源自Fortran的代码和接口中很常见。错误指示符通常是可选的,如果调用者对结果是否有效不感兴趣,可以将其留在NULL
上。
我自己的规则很简单:
在编写低级库时,首选第二种方法。对于熟悉标准C库的人来说,这是一种熟悉的方法。
使用第一种方法处理可恢复的错误。
通常,我将其与第二个相结合,使用return 0;
获得成功,并使用return errno;
或return errno = EINVAL;
等获得错误。 (最后一个将EINVAL
分配给errno
,然后返回EINVAL
。)
在应通过多次操作保留错误状态或存在状态错误影响的结构时,使用第三种方法。
让我们看看这些方法在实践中有何不同。
一个非常普通的事情是将命令行参数解析为数字。让我们看一下将参数用作double
进行某种计算的情况:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int arg;
double val;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], "%lf", &val) == 1) {
printf("argv[%d] = %.6f\n", arg, val);
} else {
printf("%s: Not a number.\n", argv[arg]);
exit(EXIT_FAILURE);
}
}
return EXIT_SUCCESS;
}
以上使用sscanf()
转换字符串。不幸的是,它不检查任何尾随的垃圾,因此它接受1.5k
作为1.5
。为了避免这种情况,我们可以使用虚拟字符来检测尾随的垃圾:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int arg;
double val;
char dummy;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], "%lf %c", &val, &dummy) == 1) {
printf("argv[%d] = %.6f\n", arg, val);
} else {
printf("%s: Not a number.\n", argv[arg]);
exit(EXIT_FAILURE);
}
}
return EXIT_SUCCESS;
}
之所以可行,是因为sscanf()
返回成功的转换次数,并且我们期望只有双精度转换(%lf
)有效,而char转换(%c
)却会失败。
不幸的是,scanf系列函数不会检查溢出。如果您提供足够大的数量,它将被默默地粉碎。不好。为了避免这种情况,我们可以使用strtod()
。为了简化使用,我们可以将其放在单独的函数parse_double()
中。但是,那该如何返回值以及可能的错误呢?实施以下哪一项?
/* Convert the initial double, returning the pointer to the rest of the
string; or NULL if an error occurs. */
const char *parse_double(const char *src, double *to);
/* If the string contains exactly one double, convert it and return 0.
Otherwise return a nonzero error code. */
int parse_double(const char *src, double *to);
/* Convert the string to a double as best as you can. If an error occurs, return 'errval'. */
double parse_double(const char *src, const double errval);
那么,哪个是最好的?
答案当然是取决于用例。
我实际上已经实现了这三个(在单独的程序中),具体取决于哪一个是最合适的。
当使用相同功能解析输入文件时,和/或我们允许每个参数/行使用任意数量的双精度数时,第一个尤为有用。在循环中使用非常容易。
第二个是我在程序中最常使用的东西。我经常使用
typedef struct {
double x;
double y;
double z;
} vec3d;
int parse_vector(const char *src, vec3d *to)
{
vec3d temp;
char dummy;
if (!src || !*src)
return -1; /* NULL or empty string */
if (sscanf(src, " %lf %lf %lf %c", &temp.x, &temp.y, &temp.z, &dummy) == 3 ||
sscanf(src, " %lf %*[.,:/] %lf %*[.,:/] %lf %c", &temp.x, &temp.y, &temp.z, &dummy) == 3) {
if (to)
*to = temp;
return 0;
}
return -1;
}
允许使用1+2+3
,1/2/3
,1:2:3
甚至'1 2 3'
或"1 2 3"
在命令行上指定3D向量(引号需要阻止外壳将其拆分为三个单独的参数)。它不会检查double
的溢出情况,因此在输出中显示已解析的向量很重要,以便用户可以检测到他们的输入是否被错误地解析。
(*
中的星号%*[.,:/]
表示转换结果未存储在任何地方,并且转换未计入返回值。[
是转换说明符,“转换”列表中的所有字符,并以]
字符结尾。[^
是相反的,“转换”列表中的所有和所有字符 not 。 )
答案 1 :(得分:2)
不要使用指针返回带外错误代码。指针为NULL或有效(如果无效),通常在程序中堆栈溢出。 char *pointer = (char*)(uintptr_t)1;
只是令人困惑,而if ((uintptr_t)pointer == 1) {.. }
则难以维护。
返回int
。 int
在C标准库中很常见,会返回错误。通常,C库在发生错误时返回-1
并设置errno-我通常编写的库代码返回的是错误代码的负值(即return -ENOMEM
在malloc
失败的情况下)。成功返回0
并返回正值,以通知用户代码有关库中某些“状态”的信息。通过指针传递要设置的所有变量。看前fopen_s(观点:不要使用fopen_s
,只看它)。
enum importantFunction_rets_e {
IMPORTANT_FUNCTION_ERR_1 = -1,
IMPORTANT_FUNCTION_ERR_2 = -2,
IMPORTANT_FUNCTION_STATE_1 = 1,
IMPORTANT_FUNCTION_STATE_2 = 2,
};
int importantFunction(char **pointer)
{
assert(pointer != NULL);
// or maybe
if (pointer == NULL) return -EINVAL;
int ret;
ret = secondFunction(pointer);
if (ret < 0) return ret;
ret = thirdFunction(pointer);
if (ret < 0) return ret;
return 0;
}
int secondFunction(char **pointer) {
*pointer = malloc(sizeof(char) * 5);
if (*pointer == NULL) {
return IMPORTANT_FUNCTION_ERR_1;
}
memcpy(*pointer, "hey!", 5);
return 0;
}
int main() {
char *pointer;
const int importantFunction_ret = importantFunction(&pointer);
if (importantFunction_ret < 0) {
if (importantFunction_ret == IMPORTANT_FUNCTION_ERR_1) {
// handle err 1
} else if (importantFunction_ret == IMPORTANT_FUNCTION_ERR_2) {
// handle err 2
} else {
// hanlde other errors
}
return -1;
}
if (importantFunction_ret == IMPORTANT_FUNCTION_STATE_1) {
// handle state1
} else if {importantFunction_ret == IMPORTANT_FUNCTION_STATE_2) {
// handle state2
} else {
// handle other states
assert(0);
}
}
如果您想探索C语言中的主题或错误处理方法,则可以沿新的(或旧的吗?)proposal实施某些与面向对象语言使用std::variant
相同的方法。或std::expected
或类似名称(观点:我现在确实反对该建议,它需要重新设计/重构,但这对于C而言将是一大进步)。
答案 2 :(得分:1)
在C中处理错误的一种常见方法是通过返回值。
说一个函数f
成功后,返回一个指向字符串的指针。
char *f();
此类函数在失败时将返回NULL
指针,您可以通过包含一些常见的头文件(例如<string.h>
)来获得该指针。
现在说g
是一个给定整数的函数,它计算某些内容并返回该运算的整数结果,但是该函数可能会失败(例如,该参数对于计算无效,谁知道...) 。那也许你想这样写
int g(int i, int *result);
在这里,i
是计算内容的参数,而result
是指向将用于存储结果的变量的指针。现在,为什么g
返回输入int
吗?好吧,它可能是bool
中的<stdbool.h>
,但是通常使用int
...返回值将用作布尔值,g
将返回0
失败则1
成功。
您可以在第三个函数h
int h(int i) {
char *str = f();
if (str == NULL) {
printf("f failed !\n");
return 0; // f failed
}
printf("%s\n", str);
int result;
if (!g(i, &result)) {
printf("g failed !\n");
return 0; // g failed
} else {
printf("result = %d\n", result);
}
return 1; // h success
}
答案 3 :(得分:0)
我意识到的一件事是,将致命错误渗透到堆栈中通常是不值得的。如果某件事以无法前进的方式失败,则在该处结束程序。我通常通过创建一个error_exit
函数来处理它,该函数可以从任何地方调用:
void error_exit(int code, const char* message) {
printf("Error %d: %s\nExiting!\n", code, message);
cleanup();
exit(code);
}
float* nested_function(int input, ...) {
if (causes_hopeless_failure(input)) {
error_exit(err_HOPELESS, "invalid input to nested_function");
}
//normal processing proceeds ...
return valid_pointer;
}
int main() {
float* vector = function_which_eventually_calls_nested_function();
cleanup();
return 0;
}
cleanup
函数用于处理无法在程序退出时正确清理的资源。文件句柄和分配的缓冲区通常不属于此类。我通常将其用于需要撤消的系统配置更改。
答案 4 :(得分:0)
C具有带外错误代码处理功能,自那以来一直存在。
#include <errno.h>
int do_something(char* data) {
if ( data == 0 ) {
errno = ENODATA;
return 0;
}
... do stuff ...
}
...在呼叫者中...
int value = do_something( "one" );
if ( int errornum = errno ) {
fprintf("error (%d) could not do something: %s", strerror( errornum ) );
return; // or exit;
}
如果希望链接错误
int value = do_something( "one" );
if ( int errornum = errno ) {
fprintf("error (%d) could not do something: %s", strerror( errornum ) );
errno = errornum;
return; // or exit;
}
请记住,几乎每个标准函数调用都会重置errno,因此您需要捕获它,然后有选择地执行任何操作后再设置它。
errno通常没有得到应有的重视的原因可能是因为首先要教太多的人带内错误报告(通过特殊的标记/值)。另外,需要花费更多的代码行来正确检查错误代码。也就是说,这是一个更好的解决方案,因为您不会在同一个变量中用数据和控制信息重载返回值。
已经设置了很多错误代码,奇怪的是您可以根据需要重复使用一个,或者选择一个足够接近的错误代码
1 EPERM Operation not permitted
2 ENOENT No such file or directory
3 ESRCH No such process
4 EINTR Interrupted system call
5 EIO I/O error
6 ENXIO No such device or address
7 E2BIG Argument list too long
8 ENOEXEC Exec format error
9 EBADF Bad file number
10 ECHILD No child processes
11 EAGAIN Try again
12 ENOMEM Out of memory
13 EACCES Permission denied
14 EFAULT Bad address
15 ENOTBLK Block device required
16 EBUSY Device or resource busy
17 EEXIST File exists
18 EXDEV Cross-device link
19 ENODEV No such device
20 ENOTDIR Not a directory
21 EISDIR Is a directory
22 EINVAL Invalid argument
23 ENFILE File table overflow
24 EMFILE Too many open files
25 ENOTTY Not a typewriter
26 ETXTBSY Text file busy
27 EFBIG File too large
28 ENOSPC No space left on device
29 ESPIPE Illegal seek
30 EROFS Read-only file system
31 EMLINK Too many links
32 EPIPE Broken pipe
33 EDOM Math argument out of domain of func
34 ERANGE Math result not representable
35 EDEADLK Resource deadlock would occur
36 ENAMETOOLONG File name too long
37 ENOLCK No record locks available
38 ENOSYS Function not implemented
39 ENOTEMPTY Directory not empty
40 ELOOP Too many symbolic links encountered
42 ENOMSG No message of desired type
43 EIDRM Identifier removed
44 ECHRNG Channel number out of range
45 EL2NSYNC Level 2 not synchronized
46 EL3HLT Level 3 halted
47 EL3RST Level 3 reset
48 ELNRNG Link number out of range
49 EUNATCH Protocol driver not attached
50 ENOCSI No CSI structure available
51 EL2HLT Level 2 halted
52 EBADE Invalid exchange
53 EBADR Invalid request descriptor
54 EXFULL Exchange full
55 ENOANO No anode
56 EBADRQC Invalid request code
57 EBADSLT Invalid slot
59 EBFONT Bad font file format
60 ENOSTR Device not a stream
61 ENODATA No data available
62 ETIME Timer expired
63 ENOSR Out of streams resources
64 ENONET Machine is not on the network
65 ENOPKG Package not installed
66 EREMOTE Object is remote
67 ENOLINK Link has been severed
68 EADV Advertise error
69 ESRMNT Srmount error
70 ECOMM Communication error on send
71 EPROTO Protocol error
72 EMULTIHOP Multihop attempted
73 EDOTDOT RFS specific error
74 EBADMSG Not a data message
75 EOVERFLOW Value too large for defined data type
76 ENOTUNIQ Name not unique on network
77 EBADFD File descriptor in bad state
78 EREMCHG Remote address changed
79 ELIBACC Can not access a needed shared library
80 ELIBBAD Accessing a corrupted shared library
81 ELIBSCN .lib section in a.out corrupted
82 ELIBMAX Attempting to link in too many shared libraries
83 ELIBEXEC Cannot exec a shared library directly
84 EILSEQ Illegal byte sequence
85 ERESTART Interrupted system call should be restarted
86 ESTRPIPE Streams pipe error
87 EUSERS Too many users
88 ENOTSOCK Socket operation on non-socket
89 EDESTADDRREQ Destination address required
90 EMSGSIZE Message too long
91 EPROTOTYPE Protocol wrong type for socket
92 ENOPROTOOPT Protocol not available
93 EPROTONOSUPPORT Protocol not supported
94 ESOCKTNOSUPPORT Socket type not supported
95 EOPNOTSUPP Operation not supported on transport endpoint
96 EPFNOSUPPORT Protocol family not supported
97 EAFNOSUPPORT Address family not supported by protocol
98 EADDRINUSE Address already in use
99 EADDRNOTAVAIL Cannot assign requested address
100 ENETDOWN Network is down
101 ENETUNREACH Network is unreachable
102 ENETRESET Network dropped connection because of reset
103 ECONNABORTED Software caused connection abort
104 ECONNRESET Connection reset by peer
105 ENOBUFS No buffer space available
106 EISCONN Transport endpoint is already connected
107 ENOTCONN Transport endpoint is not connected
108 ESHUTDOWN Cannot send after transport endpoint shutdown
109 ETOOMANYREFS Too many references: cannot splice
110 ETIMEDOUT Connection timed out
111 ECONNREFUSED Connection refused
112 EHOSTDOWN Host is down
113 EHOSTUNREACH No route to host
114 EALREADY Operation already in progress
115 EINPROGRESS Operation now in progress
116 ESTALE Stale NFS file handle
117 EUCLEAN Structure needs cleaning
118 ENOTNAM Not a XENIX named type file
119 ENAVAIL No XENIX semaphores available
120 EISNAM Is a named type file
121 EREMOTEIO Remote I/O error
122 EDQUOT Quota exceeded
123 ENOMEDIUM No medium found
124 EMEDIUMTYPE Wrong medium type
125 ECANCELED Operation Canceled
126 ENOKEY Required key not available
127 EKEYEXPIRED Key has expired
128 EKEYREVOKED Key has been revoked
129 EKEYREJECTED Key was rejected by service
130 EOWNERDEAD Owner died
131 ENOTRECOVERABLE State not recoverable