在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(和其他)的工作的主要内容,如果未能解决你的问题,请参考以下文章

Java中条件语句和if-else的嵌套原则

有啥方法可以减少程序中条件语句if-else或者switch-case的过多嵌套?

C#-#define条件编译

C#-C#-#define条件编译

C ++如何一起使用 std::adjacent 和 std::count_if 来计算向量中条件的出现次数

解析C中条件编译,头文件包含知识,以及 #/## 的运用