C中的宏比较结果不正确。

huangapple go评论123阅读模式
英文:

Macro comparison in C has incorrect result

问题

以下是代码的翻译:

为什么以下代码的输出是 `10`,而不是预期的 `20`?

#define A double

#if A == float
    #define X 10
#elif A == double
    #define X 20
#endif

int main() {
    return X;
}

您可以尝试它:https://godbolt.org/z/r8xhq5Pja

英文:

Why output of the following code is 10 instead of the expected 20?

#define A double

#if A == float
    #define X 10
#elif A == double
    #define X 20
#endif

int main() {
    return X;
}

Try it: https://godbolt.org/z/r8xhq5Pja

答案1

得分: 5

标准规定,在预处理器条件语句中,经过宏展开后剩下的任何标识符都被视为零。floatdouble都变为0 — 因此条件的结果是#define X 10,程序返回10给环境。

请参阅(C11)§6.10.1 条件包含,特别是§4(已加重):

§4 在评估之前,将要成为控制常量表达式的预处理标记列表中的宏调用被替换(除了那些被defined一元运算符修改的宏名称),就像在正常文本中一样。如果标记defined由于这个替换过程或使用defined一元运算符而生成,不匹配宏替换之前的两种指定形式之一,行为是未定义的。 在执行了所有由于宏扩展和defined一元运算符的替换之后,所有剩下的标识符(包括在词法上与关键字相同的标识符)都被替换为pp-number 0,然后每个预处理标记都被转换成一个标记。 由此产生的标记组成控制常量表达式,根据6.6的规则进行评估。为了进行此标记转换和评估,在此目的中,所有有符号整数类型和无符号整数类型都被视为具有与头文件<stdint.h>中定义的类型intmax_tuintmax_t相同的表示。这包括解释字符常量,其中可能涉及将转义序列转换为执行字符集成员。这些字符常量的数值值是否与表达式中(除非在#if#elif指令内)出现的相同字符常量获得的值匹配是实现定义的。另外,单字符字符常量是否可以具有负值也是实现定义的。

英文:

The standard says that in a preprocessor conditional, any identifier that is left over after macro expansion is done is treated as zero. Both float and double become 0 — the result of the conditions is therefore #define X 10 and the program returns 10 to the environment.

See (C11) §6.10.1 Conditional Inclusion, especially ¶4 (emphasis added):

> ¶4 4 Prior to evaluation, macro invocations in the list of preprocessing tokens that will become the controlling constant expression are replaced (except for those macro names modified by the defined unary operator), just as in normal text. If the token defined is generated as a result of this replacement process or use of the defined unary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined. After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers (including those lexically identical to keywords) are replaced with the pp-number 0, and then each preprocessing token is converted into a token. The resulting tokens compose the controlling constant expression which is evaluated according to the rules of 6.6. For the purposes of this token conversion and evaluation, all signed integer types and all unsigned integer types act as if they have the same representation as, respectively, the types intmax_t and uintmax_t defined in the header &lt;stdint.h&gt;.<sup>167)</sup> This includes interpreting character constants, which may involve converting escape sequences into execution character set members. Whether the numeric value for these character constants matches the value obtained when an identical character constant occurs in an expression (other than within a #if or #elif directive) is implementation-defined.<sup>168)</sup> Also, whether a single-character character constant may have a negative value is implementation-defined.

答案2

得分: 4

这不是C预处理器的工作方式。

首先,由于 #define A double,你的代码被简化为以下形式:

#if double == float
    #define X 10
#elif double == double
    #define X 20
#endif
...

预处理器中的符号 doublefloat 都没有被 #define。因此,预处理器符号 doublefloat 都被隐式地视为 0,因此 #if double == float 等同于 #if 0 == 0,这当然是成立的。

在预处理器级别,doublefloat 不是C关键字,而只是预处理器令牌。

英文:

That's not how the C preprocessor works.

First your code boils down to this, because of #define A double:

#if double == float
    #define X 10
#elif double == double
    #define X 20
#endif
...

Neither of the preprocessor symbols double or float has been #defined. Therefore both preprocessor symbols double and float are implicitly 0 and therefore #if double == float is equivalent to #if 0 == 0 which is, of course, true.

At the preprocessor level, double and float are not C keywords but just preprocessor tokens.

答案3

得分: 2

作为其他回答所描述的,C预处理器不提供比较(预处理器)标记文本的功能。在条件指令中,条件首先被展开。然后,剩下的预处理标记类型为"标识符"的标记都会被替换为0。如果这导致一系列标记可以被解释为表达式,那么它必然是一个常量表达式。否则,行为是未定义的。

你在评论中说你的目标是模拟C++模板。如果你想要模板,我倾向于建议你直接使用*C++*来编写。然而,在C中有一些方法可以解决这个问题,具体取决于你想要模拟模板的哪些方面。最适用于这个任务的两个预处理器功能可能是标记粘贴和类似函数的宏。

首先来看一下标记粘贴,这是一种实现你似乎希望你的示例代码表现出的行为的方法:

#define A double

#define floatval 10
#define doubleval 20

#define JOIN_HELPER(x, y) x ## y
#define JOIN(x, y) JOIN_HELPER(x, y) 
#define X JOIN(A, val)

int main() {
    return X;
}

根据A的定义是float还是doubleJOIN(A, val)的调用将展开为floatvaldoubleval,然后进一步展开为10或20。你可以类似地生成C标识符,而不仅仅是宏名称。例如,你可以生成包含宏A展开的函数名称。

但是,类似函数的宏在这里可能更合适。考虑以下示例:

#define SUM_FUNC(type)                 \
type JOIN(sum, type)(type x, type y) { \
    return x + y;                      \
}

对于任何算术类型type,该宏将展开为一个名为sumtype的函数,接受两个type类型的参数,并将它们的和作为type返回。

英文:

As other answers have described, the C preprocessor does not provide for comparing (preprocessor) token text. In a conditional directive, the condition is first expanded. Then, any remaining preprocessing tokens of type "identifier" are replaced by 0. If this results in a sequence of tokens that can be interpreted as an expression, then it is necessarily a constant expression. Otherwise, the behavior is undefined.

You said in comments that your objective is to simulate C++ templates. I would be inclined to suggest writing in bona fide C++ if you want templates. Nevertheless, there are ways to approach the problem in C, depending on just what aspects of templates you want to simulate. The two preprocessor features most applicable to the task would probably be token pasting and function-like macros.

Taking the token-pasting first, here's a way to achieve the behavior you seem to have wanted your example code to manifest:

#define A double

#define floatval 10
#define doubleval 20

#define JOIN_HELPER(x, y) x ## y
#define JOIN(x, y) JOIN_HELPER(x, y) 
#define X JOIN(A, val)

int main() {
    return X;
}

Depending on the definition of A as either float or double, that JOIN(A, val) invocation expands to either floatval or doubleval, which in turn expands to either 10 or 20. You can use similar to generate C identifiers, not just macro names. For example, you could generate function names that incorporate the expansion of macro A.

But function-like macros seem a likely headliner here. Consider this:

#define SUM_FUNC(type)                 \
type JOIN(sum, type)(type x, type y) { \
    return x + y;                      \
}

For any arithmetic type, type, that macro will expand to a function sumtype, accepting two arguments of type type and returning their sum as a type.

huangapple
  • 本文由 发表于 2023年3月9日 23:26:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75686714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定