C语言初始化FILE*变量遇编译错误及stdin/stdout宏相关疑问
问题背景回顾
你遇到的问题是在使用FILE *usrin = stdin, *usrout = stdout;初始化变量时,GCC 8.1.1搭配glibc 2.27.9000编译报错“initializer is not a compile time constant”,并且发现stdio.h里有extern FILE *stdin;、注释/* C89/C99 say they're macros. Make them happy */以及#define stdin stdin的定义。下面逐个解答你的三个疑问:
1. 为何不能用这种常规方式初始化变量?
这里的核心限制来自C语言的初始化规则:如果usrin和usrout是全局变量或者静态局部变量,它们的初始化值必须是编译期常量。而stdin/stdout虽然看起来像全局变量,但在当前glibc的实现里,它是一个指向外部链接变量的宏,这个变量的地址要到链接阶段才能确定,不属于编译期能确定的常量。
如果你的变量是普通局部变量(函数内部非static的变量),这种初始化是完全合法的——因为局部变量的初始化是在运行时执行的,不需要编译期常量。你可以检查下代码里usrin/usrout的作用域,大概率是全局或静态变量才触发了这个错误。
2. 为何该宏展开后不是编译期常量?
glibc里的#define stdin stdin看起来是自引用的“无效”宏,但实际上这是一种符合标准的实现技巧:
- 预处理器处理这个宏时,会先展开
stdin,但因为展开后的内容还是stdin,预处理器会停止递归,最终结果就是stdin这个标识符。 - 而这个标识符对应的是
extern FILE *stdin;声明的外部变量——外部变量的存储地址是程序链接时才分配的,编译阶段编译器无法知道它的具体值,所以自然不是编译期常量。
简单说,这个宏只是让stdin符合C标准“必须是宏”的要求,本质上它还是指向一个运行时才能确定地址的外部变量。
3. 将stdin/stdout设为宏的设计依据是什么?
这个设计完全是遵循C语言标准的要求:
- 从C89(ANSI C)开始,C标准就明确规定
stdin、stdout、stderr必须是宏,而不是普通的外部变量。标准允许这些宏被重新定义(虽然实际开发中很少这么做),同时要求它们可以展开为指向FILE类型的左值。 - 这种设计也给实现方留了灵活空间:比如在多线程环境中,宏可以被展开为线程局部存储(TLS)的引用,让每个线程拥有独立的标准流;或者在某些嵌入式环境中,通过宏来动态获取流的地址,而不是固定的全局变量。
- 另外,glibc的注释也直接说明了:
/* C89/C99 say they're macros. Make them happy */——就是为了满足标准的强制要求,让实现符合规范。
内容的提问来源于stack exchange,提问作者vonbrand
相关产品推荐
相关产品推荐

