errno.h快速入门

来源:互联网 发布:cf活动领枪软件 编辑:程序博客网 时间:2024/05/19 23:29

文章出处:http://www.cnblogs.com/yaohj/archive/2011/01/28/1946817.html

 

简介:

头文件errno.h定义了一个全局的宏errno,它被展开为一个int类型的“左值”,这意味着宏errno不一定是个对象的标识符,也可以展开为一个由函数返回的可以修改的“左值”,比如int *errno(),这个后面会讲,你可以暂且把它理解为一个全局的int型变量(虽然这样理解是错的,不过方便理解)。

简单来说,errno.h只是为了提供了一种错误报告机制。比如,一个函数调用fopen()发生了错误,它可能就会去修改errno的值,这样外部的代码可以通过判断errno的值来区分fopen()内部执行时是否发生错误,并根据errno值的不同来确定具体的错误类型。
先来看一段代码,Demo1:

[cpp] view plain copy
  1. 01 #include    <stdio.h>    
  2. 02 #include    <errno.h>     
  3. 03      
  4. 04 int main ( int argc, char *argv[] )    
  5. 05 {    
  6. 06     //try to open file "whatever.txt". when you run this demo,make sure the file is NOT existed..。    
  7. 07     FILE *fp = fopen("whatever.txt","r");    
  8. 08      
  9. 09     if(fp ==NULL)    
  10. 10     {    
  11. 11         printf("Can not open file\n");    
  12. 12      
  13. 13         printf("errno value: %d, it means: %s",errno, strerror(errno));    
  14. 14     }    
  15. 15      
  16. 16     return 0;    
  17. 17 }   

程序会输出:

[cpp] view plain copy
  1. Can not open file   
  2. 2 errno value: 2, it means: No such file or directory   

strerror是标准库stdio.h定义的一个函数,它用来返回错误代码所代表的含义。如Demo1所示,我们用fopen(也在stdio.h中定义)打开一个并不存在的文件,因此返回的fp是一个空指针。而在fopen尝试打开文件失败时会修改errno的值,Demo1里fopen失败原因是文件不存在,因此fopen将会把errno指向的值修改为2,通过stderror可以看到错误代码2的意思是“No such file or directory”。

帅气,看起来用errno来报告错误,既方便,也很简单,但实际应用时远没Demo1那么简单,试下Demo2:

[cpp] view plain copy
  1. 01 #include    <stdio.h>    
  2. 02 #include    <errno.h>    
  3. 03 #include    <math.h>      
  4. 04      
  5. 05 int main ( int argc, char *argv[] )    
  6. 06 {    
  7. 07     /*try to open file "whatever.txt". when you run this demo,make sure the file is NOT existed...*/   
  8. 08     FILE *fp = fopen("whatever.txt","r");    
  9. 09      
  10. 10     if(fp ==NULL)    
  11. 11     {   
  12. 12         printf("Can not open file\n");    
  13. 13      
  14. 14         int root = sqrt(123568 -123668 );    
  15. 15      
  16. 16         printf("errno value: %d, it means: %s",errno, strerror(errno));    
  17. 17     }    
  18. 18      
  19. 19     return 0;    
  20. 20 }   


程序会输出:

[cpp] view plain copy
  1. Can not open file   
  2. 2 errno value: 33, it means: Numerical argument out of domain   

 

这跟我们期望的错误信息完全不一样,因为sqrt函数在得到一个非法的参数时,它把errno的值修改成了33,覆盖了fopen设置的错误代码。所以,使用errno一个比较安全的编码方式是,在下个库函数调用之前就查看它的值,千万不要因为某个库函数看似很简单就假设它不会修改errno的值,那样会死的很惨……如果必须在查看errno之前调用别的库函数,一种安全的方式是先把errno的值保存到一个临时变量里,然后调用那个“必须”调用的库函数,处理完毕后再把errno恢复到之前的值。如Demo3:

[cpp] view plain copy
  1. 01 #include    <stdio.h>    
  2. 02 #include    <errno.h>    
  3. 03 #include    <math.h>      
  4. 04      
  5. 05 double getSqrt(double value)    
  6. 06 {    
  7. 07     //1、save last errno to a temp variable    
  8. 08     int tmpErrno = errno;    
  9. 09     //2、set errno to 0    
  10. 10     errno = 0;    
  11. 11      
  12. 12     double root = sqrt(123568 -123668 );    
  13. 13      
  14. 14     printf("I changed errno to '%d' sliently...but it's safe \n",errno);    
  15. 15      
  16. 16     //3.restore errno    
  17. 17     errno = tmpErrno;    
  18. 18      
  19. 19     return root;    
  20. 20 }    
  21. 21      
  22. 22 int main ( int argc, char *argv[] )    
  23. 23 {    
  24. 24     FILE *fp = fopen("whatever.txt","r");    
  25. 25      
  26. 26     if(fp ==NULL)    
  27. 27     {    
  28. 28         printf("Can not open file\n");    
  29. 29      
  30. 30         getSqrt(-1);    
  31. 31      
  32. 32         printf("errno value: %d, it means: %s",errno, strerror(errno));    
  33. 33     }    
  34. 34      
  35. 35     return 0;    
  36. 36 }   

程序会输出:

[cpp] view plain copy
  1. Can not open file   
  2. 2 I changed errno to '33' sliently...but it's safe    
  3. 3 errno value: 2, it means: No such file or directory   


现在,就像期望的那样输出了。
但是……
这样真的安全么?!想像一下,如果errno是个全局变量,那多线程环境下岂不完蛋了?!本来线程A把errno设置成2,还没执行到查看错误的语句时,线程B就把errno设置成了33,然后线程A才开始查看errno并输出错误信息,而这时输出的错误就很让人抓狂了!神呀,这破东西多线程没法儿用哇!
但是……
你多虑了……文章开始说过,宏errno可以被展开为一个“左值”,比如int* getYourErrno(),所以你可以在getYourErrno()里返回一个线程内的局部变量,这样不管哪个线程修改errno都修改的它自己的局部变量,所以我们担心的问题是不存在的。看下errno.h的源码就明白了

[cpp] view plain copy
  1. 01 /* Get the error number constants from the system-specific file.   
  2. 02    This file will test __need_Emath and _ERRNO_H.  */   
  3. 03 #include <bits/errno.h>    
  4. 04 #undef  __need_Emath    
  5. 05      
  6. 06 #ifdef  _ERRNO_H    
  7. 07      
  8. 08 /* Declare the `errno' variable, unless it's defined as a macro by   
  9. 09    bits/errno.h.  This is the case in GNU, where it is a per-thread   
  10. 10    variable.  This redeclaration using the macro still works, but it   
  11. 11    will be a function declaration without a prototype and may trigger   
  12. 12    a -Wstrict-prototypes warning.  */   
  13. 13 #ifndef errno    
  14. 14 extern int errno;    
  15. 15 #endif   


上面的注释说了,如果errno没有定义过就把errno定义为“extern int errno;”,如果这样多线程时是会发生悲剧的,先不着急哭,我们去前面看看它是否被定义过,前面的代码include了一个叫bits/errno.h的头文件,看名字就很“险恶”,进去看看:

[cpp] view plain copy
  1. 1 # ifndef __ASSEMBLER__    
  2. /* Function to get address of global `errno' variable.  */   
  3. extern int *__errno_location (void) __THROW __attribute__ ((__const__));    
  4. 4      
  5. 5 #  if !defined _LIBC || defined _LIBC_REENTRANT    
  6. /* When using threads, errno is a per-thread value.  */   
  7. 7 #   define errno (*__errno_location ())    
  8. 8 #  endif    
  9. 9 # endif /* !__ASSEMBLER__ */   

 

果然来者不善……如果没定义宏__ASSEMBLER__,就会执行中间的代码,里面的代码又说了,如果你没定义_LIBC或者定义了_LIBC_REENTRANT他们就会把errno定义为__errno_location,一看到宏_LIBC_REENTRANT里面有“reentrant(重入)”就知道它不是个好东西……不是,是看到它的名字就知道它是跟多线程有关的,所以如果要在多线程环境下正确的使用errno,你需要确保__ASSEMBLER__没有被定义,而且_LIBC没被定义或者定义了_LIBC_REENTRANT。
可以写个程序看下自己开发环境里这几个宏的设置:

[cpp] view plain copy
  1. 01 #include <stdio.h>    
  2. 02 #include <errno.h>   
  3. 03      
  4. 04 int main( void )    
  5. 05 {    
  6. 06 #ifndef __ASSEMBLER__    
  7. 07         printf( "__ASSEMBLER__ is NOT defined!\n" );    
  8. 08 #else    
  9. 09         printf( "__ASSEMBLER__ is defined!\n" );    
  10. 10 #endif    
  11. 11      
  12. 12 #ifndef __LIBC    
  13. 13         printf( "__LIBC is NOT defined\n" );    
  14. 14 #else    
  15. 15         printf( "__LIBC is defined!\n" );    
  16. 16 #endif    
  17. 17      
  18. 18 #ifndef _LIBC_REENTRANT    
  19. 19         printf( "_LIBC_REENTRANT is NOT defined\n" );    
  20. 20 #else    
  21. 21         printf( "_LIBC_REENTRANT is defined!\n" );    
  22. 22 #endif    
  23. 23      
  24. 24         return 0;    
  25. 25 }   


我的输出:

[cpp] view plain copy
  1. _ASSEMBLER__ is NOT defined!    
  2. 2 __LIBC is NOT defined!    
  3. 3 _LIBC_REENTRANT is NOT defined!   


哈!看来我可以在多线程下安全的使用errno,如果你的默认环境不可以就在makefile里定义上_LIBC_REENTRANT吧!
ok,有关errno.h的介绍到此就结束,休息,休息一下!