学习APUE高级I/O一章中的第一个程序就遇到了一个小问题,给出该程序完整代码如下:
- #include "apue.h"
- char buf[500000];
- static void set_fl(int fd, int flags)
- {
- int val = fcntl(fd, F_GETFL, 0);
- if (val < 0)
- err_sys("fcntl F_GETFL error");
- val |= flags;
- if (fcntl(fd, F_SETFL, val) < 0)
- err_sys("fcntl F_SETFL error");
- }
- static void clr_fl(int fd, int flags)
- {
- int val = fcntl(fd, F_GETFL);
- if (val < 0)
- err_sys("fcntl F_GETFL error");
- val &= ~flags;
- if (fcntl(fd, F_SETFL, val) < 0)
- err_sys("fcntl F_SETFL error");
- }
- int main(void)
- {
- int ntowrite = read(STDIN_FILENO, global_buf, sizeof global_buf);
- fprintf(stderr, "read %d bytes.\n", ntowrite);
- set_fl(STDOUT_FILENO, O_NONBLOCK);
- char *ptr = buf;
- while (ntowrite > 0) {
- errno = 0;
- int nwrite = write(STDOUT_FILENO, ptr, ntowrite);
- fprintf(stderr, "nwrite %d, errno = %d\n", nwrite, errno);
- if (nwrite > 0) {
- ptr += nwrite;
- ntowrite -= nwrite;
- }
- }
- clr_fl(STDOUT_FILENO, O_NONBLOCK);
- exit(0);
- }
此处对STDOUT_FILENO设置了非阻塞标志
对于一个大文件infile(大小约为1M),运行./a.out < infile 2> stderr.out,即将STDERR_FILENO重定向到stderr.out,打印出每次写的字节数和errno值,由于设置了非阻塞标志,不会一次写完,所以常会引发错误EAGAIN,想观察是否会引发其它标志,所以写了如下代码:
- static int errno_array[128];
- static int arraysize;
- static void add_errno(int errno)
- {
- int i;
- if (errno == 0) return;
- for (i = 0; i < arraysize; i++) {
- if (errno_array[i] == errno) return;
- }
- errno_array[i] = errno;
- arraysize++;
- }
编译时发生如下错误
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变量,而是由宏实现的!
为更好地描述这个问题,写了以下简化代码:
- #include <stdio.h>
- int *errno_location(void)
- {
- static int errno = 5;
- return &errno;
- }
- #define errno (*errno_location())
- void func(int errno)
- {
- printf("%s\n", STR(errno));
- }
- int *func2(void)
- {
- printf("china\n");
- return NULL;
- }
- int main(void)
- {
- func(func2);
- return 0;
- }
倘若在程序中我们不知道宏展开后到底是什么样,对于因为宏展开出现的各种奇怪问题,有两种方法解决:
一种是利用 gcc -E 可查看只经过预编译后的代码,另一种是利用如下宏
- #include <stdio.h>
- #define STR(s) STR_(s)
- #define STR_(s) #s
- int main(void)
- {
- printf("%s", STR(errno));
- return 0;
- }
即可得到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;
阅读(3033) | 评论(0) | 转发(0) |