在c中条件编译#if和#else(和其他)的工作
Posted
技术标签:
【中文标题】在c中条件编译#if和#else(和其他)的工作【英文标题】:Working of conditional compilation #if and #else (and others) in c 【发布时间】:2019-02-02 18:31:56 【问题描述】:我尝试使用一些条件编译预处理指令而不是“if-else”来编写程序,如下所示。
#include<stdio.h>
int main ()
int x;
scanf ("%d",&x);
#if (x==5)
printf ("x is 5");
#else
printf ("x not 5");
#endif
但问题是,即使 x
的值是 5
,它也总是打印 else 部分。我最简单的问题是----->为什么?
是否有可能成功完成这个程序(即从用户那里获取 x 的值并使用 #if 指令和 #if 下的打印语句检查条件)。
在编译期间它显示警告 "'x' 未定义,计算结果为 0"。但x
对我来说似乎已定义。这是否意味着x
应该使用#define
定义。请解释一下条件编译背后的概念。
【问题讨论】:
预处理器不能使用 C 程序中的变量。 预处理器在编译期间运行。要检查运行时值,请使用普通的if
。
【参考方案1】:
x
不是整数文字或整数文字表达式(整数文字 + 运算符)或扩展为这些的宏,因此在条件中,预处理器将其替换为 0
(6.10.1p4)。 0==5
为 false,因此采用 #else
分支。
预处理器不知道 C 声明、类型等。它仅适用于令牌(以及最终扩展到这些令牌的宏)。
6.10.1p4
由于宏扩展和定义的一元的所有替换 运算符已执行,所有剩余的标识符(包括 词法上与关键字相同的)被替换为 pp-number 0,然后将每个预处理令牌转换为一个令牌。
【讨论】:
@LightnessRacesinOrbit 我添加了来自标准的引用来支持该声明。 ;-)(你现在欠我一个赞成票:P)。 直到!我要去科利鲁收集对你不利的证据,然后震惊地离开了:P 感谢您的参考【参考方案2】:预处理发生在编译之前。所以预处理器对你的 C 代码或变量一无所知。您不能在条件中使用任何 C 变量。
条件编译有不同的用途。
#define DEBUG
/* ....*/
#ifdef DEBUG
printf("Some debug value %d\n", val);
#endif
【讨论】:
如果预处理发生在编译之前,为什么用Clang编译这两行void a[3];
和#error
会在错误消息之前为第一行产生错误消息(数组元素类型不完整)第二行?
@EricPostpischil:有 as if 规则。允许编译器以不同的方式做事,并且不需要在进行语义分析之前运行整个预处理器阶段。该标准并不禁止您所说的您所期望的;它也不禁止您报告发生的事情。
@JonathanLeffler:我没有问为什么 C 标准允许这样做。我问 P__J__ 的陈述如何与 Clang 的行为一致。 C 标准的规范与 Clang 在这方面的行为是一致的。断言在编译之前进行预处理与 Clang 的行为不一致。
@EricPostpischil clang 在这种情况下不符合要求:除非它是通过条件包含跳过的组的一部分,否则实现不应成功翻译包含#error 预处理指令的预处理翻译单元。
5.1.1.2 翻译阶段 中的@EricPostpischil 标准表明预处理发生在编译之前。第 7 步:对生成的标记进行句法和语义分析,并将其作为翻译单元进行翻译。【参考方案3】:
#if
语句中的操作数只能是常量、用#define
定义的东西以及特殊的defined
运算符。表达式中的任何其他标识符都替换为 0。示例代码中的 x
未使用 #define
定义,因此 (x==5)
变为 (0==0)
。
在 C 2018 标准中,第 6.10.1 条告诉我们,#if
语句中的表达式评估包括:
#define
定义的东西)根据它们的定义被替换。
defined
运算符的使用被替换为 0 或 1。
所有剩余的标识符都替换为 0。
由于您的示例代码中的x
没有使用#define
定义,因此在#if
语句中将其替换为0。这会导致(0==5)
,这是错误的,因此#if
和#else
之间的代码将被跳过。
在预处理器语句中,您不能根据将在程序执行期间设置的值来评估变量。
【讨论】:
【参考方案4】:它是“预处理器”。 “前”的意思是“之前”。
您正在尝试在预处理期间使用 runtime 值!当然,预处理器在构建期间无法访问该信息。
这个问题不仅限于运行时值,而是更基本的问题。即使您尝试使用(命名的)编译时常量,例如constexpr int x = 2
,您也不能这样做。这是两种交错的语言,比如用 php 生成 html; HTML 不知道 PHP 变量,PHP 也不知道用户在页面上单击了哪些小部件。这些是完全不同的执行上下文,没有内置交互或交叉兼容性。
【讨论】:
个人对反对票感到好奇。我认为这是一个不错的类比。 通过区分“预处理器”,这个答案告诉我们有预处理器活动和处理器活动,并且预处理器活动发生在处理器活动之前,但它没有告诉我们预处理器活动和处理器活动中的内容。因此,如果x
不是预处理器标识符,它不会告诉 OP 只有在处理器活动期间才能评估 x == 5
。它甚至不区分什么是预处理器标识符和其他类型的标识符。
此外,“即使您尝试使用编译时常量”也不正确,因为2+2+1
是编译时常量,但也可以用于预处理器表达式。
@EricPostpischil 一个编译时常量,例如constexpr int x = 2
,它没有也不能有任何运行时组件,但对预处理器来说是完全不可见的。所以,让我们把它改成“命名编译时常量”。
@EricPostpischil 我没有得到你的第一条评论,所以也许你可以重写它?不,我没有对标识符类型进行学术探索——不过我认为这不值得 -2。以上是关于在c中条件编译#if和#else(和其他)的工作的主要内容,如果未能解决你的问题,请参考以下文章
有啥方法可以减少程序中条件语句if-else或者switch-case的过多嵌套?