分类: LINUX
2011-11-21 12:48:54
此处对STDOUT_FILENO设置了非阻塞标志
对于一个大文件infile(大小约为1M),运行./a.out < infile 2> stderr.out,即将STDERR_FILENO重定向到stderr.out,打印出每次写的字节数和errno值,由于设置了非阻塞标志,不会一次写完,所以常会引发错误EAGAIN,想观察是否会引发其它标志,所以写了如下代码:
编译时发生如下错误
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变量,而是由宏实现的!
为更好地描述这个问题,写了以下简化代码:
倘若在程序中我们不知道宏展开后到底是什么样,对于因为宏展开出现的各种奇怪问题,有两种方法解决:
一种是利用 gcc -E 可查看只经过预编译后的代码,另一种是利用如下宏
即可得到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;