0x0B-C语言错误处理

  • 三个必要的头文件

    1. #include <stdio.h>
    2. #include <errno.h>
    3. #include <string.h>
  • 一个声明

    1. extern int errno;
  • 四个重要函数

    1. int ferror(FILE* stream);
    2. int feof(FILE* stream);
    3. char *strerror(int errnum);
    4. void perror(const char *s);
  • 以上是在错误处理中常用的操作,前两者是必要包含的,最后的函数则是酌情使用

    1. ferror
      • 检查流中是否有错误,如果有则返回一个非0值,如果没有错误则返回 0
    2. feof

      • 用于检查是否到了文件尾,经常用于文件的读取工作,读取到文件尾则返回非零值

        1. input = fopen("file.in", "r");
        2. while(!feof(input))
        3. {
        4. fscanf(input, "%s", str);
        5. ...
        6. 等同于
        7. do{
        8. rtn_value = fscanf(input, "%s", str);
        9. ...
        10. }while(rtn_value != EOF);
    3. strerror

      • 当某个函数或者操作触发了设置 errno 变量的时候,我们可以立即使用该函数进行显示输出

        1. strerror(errno);
        2. 输出: Too many open files
    4. perror

      • 与上方的函数相同,也是用来输出错误的,只不过它自动使用 errno 变量,并且在自前方添加所传入的参数以及一个冒号

        1. perror("Now");
        2. 输出: Now: Too many open files
  • 同样的,对于自己的错误处理,可以设计一个小体系来满足自我需求

    • 通过返回值

      1. /* fun.h */
      2. void fun_error(size_t errnum);
      3. size_t test_fun(int arg);

      在此我们可以让外界接触不到真实的包含有错误的数组或其他数据结构,而是将其放于.c文件中隐藏起来,通过一个函数来进行访问。

    • 隐藏错误实现

      1. /* fun.c */
      2. static const char * fun_err[] = { "Computing nagative!",
      3. "Invalid argument",
      4. "Bad result" };
      5. size_t test_fun(int arg) { ... }
      6. void fun_error(size_t errnum) { ... }

      对于需要进一步规格化的设计来说,可以使用 enum 或者 #define 来设定每个错误返回值的名字

    • 设定名字

      1. /*fun.h*/
      2. #define ERR_CMPUTING 0
      3. #define ERR_INVARG 1
      4. #define ERR_BADRLT 2
      5. /* 或者 */
      6. enum { ERR_COMPUTING = 0,
      7. ERR_INVALIDARG,
      8. ERR_BADRESULT };

      如此我们便可以在函数中使用错误的名字代号,而不是去记下每个数字的含义。对此也可以不使用数组这个数据结构来保存错误信息,而采用 switch 在代码中实现错误的处理

    • switch错误选择·

      1. /*fun.c*/
      2. /*
      3. static const char * fun_err[] = { "Computing nagative!",
      4. "Invalid argument",
      5. "Bad result" };
      6. */
      7. ...
      8. void fun_error(size_t errnum)
      9. {
      10. switch(errnum)
      11. {
      12. case ERR_CMPUTING /*ERR_COMPUTING*/:
      13. fprintf(stderr, "...");
      14. break;
      15. case ERR_INVARG /*ERR_INVALIDARG*/:
      16. fprintf(stderr, "...");
      17. break;
      18. ...
      19. default :
      20. ...
      21. }
      22. return;
      23. }

      如此做的好处便是,不再需要去安排数组的个数,而是直接在代码中展示。并且可以加上一些预处理,来实现开关错误显示。

    • 开关错误

      1. /* fun.c */
      2. ...
      3. void fun_error(size_t errnum)
      4. {
      5. #if !defined(NOT_DEBUG)
      6. switch(errnum)
      7. {
      8. ...
      9. }
      10. #endif
      11. return;
      12. }
    • 慎重使用

      • 在我设计程序的过程中,尽量让错误提示减少,或者说用户不应该接触到错误以及处理错误,所以当一个程序没有必要提供给用户错误信息的时候,才是真正的完整程序,至于程序失败那又是量一种境况,例如备份程序可能由于文件的属性,而复制失败,这是不可避免的,此时将失败信息写入文档,呈现给用户即可。