Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192004
  • 博文数量: 27
  • 博客积分: 725
  • 博客等级: 上士
  • 技术积分: 347
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-04 09:01
文章分类

全部博文(27)

文章存档

2012年(15)

2011年(12)

分类: LINUX

2011-11-21 12:48:54

 学习APUE高级I/O一章中的第一个程序就遇到了一个小问题,给出该程序完整代码如下:
  1. #include "apue.h"

  2. char buf[500000];

  3. static void set_fl(int fd, int flags)
  4. {
  5.     int val = fcntl(fd, F_GETFL, 0);
  6.     if (val < 0)
  7.         err_sys("fcntl F_GETFL error");
  8.     val |= flags;
  9.     if (fcntl(fd, F_SETFL, val) < 0)
  10.         err_sys("fcntl F_SETFL error");
  11. }

  12. static void clr_fl(int fd, int flags)
  13. {
  14.     int val = fcntl(fd, F_GETFL);
  15.     if (val < 0)
  16.         err_sys("fcntl F_GETFL error");
  17.     val &= ~flags;
  18.     if (fcntl(fd, F_SETFL, val) < 0)
  19.         err_sys("fcntl F_SETFL error");
  20. }

  21. int main(void)
  22. {
  23.     int ntowrite = read(STDIN_FILENO, global_buf, sizeof global_buf);
  24.     fprintf(stderr, "read %d bytes.\n", ntowrite);
  25.     set_fl(STDOUT_FILENO, O_NONBLOCK);
  26.     char *ptr = buf;
  27.     while (ntowrite > 0) {
  28.         errno = 0;
  29.         int nwrite = write(STDOUT_FILENO, ptr, ntowrite);
  30.         fprintf(stderr, "nwrite %d, errno = %d\n", nwrite, errno);
  31.         if (nwrite > 0) {
  32.             ptr += nwrite;
  33.             ntowrite -= nwrite;
  34.         }
  35.     }
  36.     clr_fl(STDOUT_FILENO, O_NONBLOCK);
  37.     exit(0);
  38. }

此处对STDOUT_FILENO设置了非阻塞标志

对于一个大文件infile(大小约为1M),运行./a.out < infile 2> stderr.out,即将STDERR_FILENO重定向到stderr.out,打印出每次写的字节数和errno值,由于设置了非阻塞标志,不会一次写完,所以常会引发错误EAGAIN,想观察是否会引发其它标志,所以写了如下代码:

  1. static int errno_array[128];
  2. static int arraysize;
  3. static void add_errno(int errno)
  4. {
  5.     int i;
  6.     if (errno == 0) return;
  7.     for (i = 0; i < arraysize; i++) {
  8.         if (errno_array[i] == errno) return;
  9.     }
  10.     errno_array[i] = errno;
  11.     arraysize++;
  12. }

编译时发生如下错误

warning: passing argument 1 of ‘add_errno’ makes pointer from integer without a cast
note: expected ‘int * (*)()’ but argument is of type ‘int’

仔细分析错误原因,是因为errno的问题。倘若errno是一个全局int变量就不会出现问题,在定义add_errno时的传入参数errno会屏蔽全局的errno,而调用者add_errno(errno)会传入全局errno值也不会出现问题。问题的关键所在为errno不是全局int变量,而是由宏实现的!

为更好地描述这个问题,写了以下简化代码:

  1. #include <stdio.h>

  2. int *errno_location(void)
  3. {
  4.     static int errno = 5;
  5.     return &errno;
  6. }

  7. #define errno (*errno_location())

  8. void func(int errno)
  9. {
  10.     printf("%s\n", STR(errno));
  11. }

  12. int *func2(void)
  13. {
  14.     printf("china\n");
  15.     return NULL;
  16. }

  17. int main(void)
  18. {
  19.     func(func2);
  20.     return 0;
  21. }

倘若在程序中我们不知道宏展开后到底是什么样,对于因为宏展开出现的各种奇怪问题,有两种方法解决:

一种是利用 gcc -E 可查看只经过预编译后的代码,另一种是利用如下宏 

  1. #include <stdio.h>

  2. #define STR(s) STR_(s)
  3. #define STR_(s) #s

  4. int main(void)
  5. {
  6.     printf("%s", STR(errno));
  7.     return 0;
  8. }

即可得到errno的展开式为 (*errno_location())

回到上面的例子,例中func传入的参数int errno被展开成了int (*errno_location()),相当于传入了一个函数 int *errno_location(),因此如果调用func时传入一个整数,编译器就会报出如下错误:

warning: passing argument 1 of ‘func’ makes pointer from integer without a cast
note: expected ‘int * (*)()’ but argument is of type ‘int'


意思为func本来预期传入一个int *(*)()的,可是调用者传入的是int。
如果传入的是func2就不会发生错误,而且能正常运行。


可见这里关键问题在于宏的展开,宏在什么情况下会展开?若在main中调用func(errno),而func定义为void func(int errno)。如果全局errno是一个整数就不存在这个问题,全局errno在func传入参数时被同名的errno屏蔽了,而在调用func时传入errno即为全局的errno,因此可以正常运作。

关键问题在于errno是一个奇怪的宏,
#define errno (*errno_location())
   

那么在func定义和func调用过程中传入的errno都会被展开。在调用func(errno)时没有问题,因为展开后返回一个整数,但是在func定义中就会产生问题,原意是定义为传入参数为整数的,而宏展开后不知不觉产生了传入参数应该为int *(*)()类型的效果,因此此处极易出错。

同样,不可给新定义的errno赋值,如 int errno = 10;将被展开为

int (*errno_location()) = 10 意为定义一个函数指针并指向地址10,这显然与原意不符。


但是errno = 10可以,被展开为

(*errno_location()) = 10;

 

阅读(2905) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~