【问题标题】:What is this mysterious macro plus sign in stdint.h?stdint.h 中这个神秘的宏加号是什么?
【发布时间】:2011-11-30 08:27:02
【问题描述】:

请看我的代码:

#include <stdint.h>

int main(int argc, char *argv[])
{
unsigned char s = 0xffU;
char ch = 0xff;
int val = 78;
((int8_t) + (78)); /*what does this mean*/

INT8_C(val);    /*equivalent to above*/

signed char + 78; /*not allowed*/

return 0;
}

我发现&lt;stdint.h&gt;中的宏定义是:

#define INT8_C(val) ((int8_t) + (val))

这个加号的意义或意义是什么?

【问题讨论】:

  • 应该注意INT8_C的这个定义是错误的:7.18.4第3段:“每次调用这些宏之一(注意:指INTN_C)都应该扩展为一个整数常量表达式适合在#if 预处理指令中使用。” 该转换在预处理器指令中不起作用,因为在预处理期间不存在类型(以及因此转换)。您使用的是什么编译器/平台?您应该考虑提交有关此问题的错误报告。
  • @ChrisLutz:在 C99 中有效;它在纳入 N1256 的技术勘误 1 中无效。这是对DR #209的回应。
  • @KeithThompson - 啊。我有 TC2 的副本,直到最近才真正了解技术勘误。

标签: c macros stdint


【解决方案1】:

sn-p:

((int8_t) + (78));

是一个表达式,一个接受值78,应用一元+,然后将其转换为int8_t类型,然后将其丢弃。它与法律表达没有真正的不同:

42;
a + 1;

它还评估表达式然后丢弃结果(尽管如果编译器可以判断没有副作用,这些可能会被优化不存在)。

这些“裸”表达式在 C 中完全有效,并且通常仅在它们具有副作用时才有用,例如 i++,它计算 i 并将其丢弃,副作用是增加值。

应该使用该宏的方式更像是:

int8_t varname = INT8_C (somevalue);

可以在标准中找到看似多余的一元+ 运算符的原因。引用 C99 6.5.3.3 Unary arithmetic operators /1:

一元+或-运算符的操作数应具有算术类型;

并且,在6.2.5 Types, /18

整数和浮点类型统称为算术类型。

换句话说,一元 + 阻止您使用宏中的所有其他数据类型,例如指针、复数或结构。

最后,你的原因是:

signed char + 78;

sn-p 不起作用是因为它不是一回事。这个开始声明一个signed char 类型的变量,但是当它到达+ 时会窒息,因为这不是一个合法的变量名。为了使其等同于您的工作 sn-p,您将使用:

(signed char) + 78;

这是将值+78 转换为类型signed char

并且,根据 C99 7.8.14 Macros for integer constants /2,您还应该小心在这些宏中使用非常数,它们不能保证有效:

这些宏的任何实例中的参数都应该是一个无后缀的整数常量(如 在 6.4.4.1 中定义),其值不超过相应类型的限制。

6.4.4.1 简单地指定各种整数格式(十进制/八进制/十六进制)和各种后缀(UULULLLLL 和小写等效项,取决于类型)。底线是它们必须是常量而不是变量。

例如,glibc 有:

# define INT8_C(c)      c
# define INT16_C(c)     c
# define INT32_C(c)     c
# if __WORDSIZE == 64
#  define INT64_C(c)    c ## L
# else
#  define INT64_C(c)    c ## LL
# endif

这将允许您的 INT8_C 宏正常工作,但文本 INT64_C(val) 将被预处理为 valLvalLL,您都不需要。

【讨论】:

  • 实际上,根据我的阅读,OP 对INT8_C 的定义违反了标准。 7.18.4 第 3 段:“这些宏之一的每次调用都应扩展为一个整数常量表达式适用于#if 预处理指令。INT8_C 的原始定义不能在#if 指令中使用,因此不正确。
  • @Chris:+ 使它在预处理指令中有效。 int8_t 扩展为 0,(0)+(val) 就是 val
  • int8_t如何扩展为0?我以为是typedef? o_O
  • @Karl - 预处理器不知道的任何标识符都会被表达式中的预处理器评估为 0。
  • 啊,我明白了,它并不关心在其他地方是否有 int8_t 的 typedef,因为 typedef 超出了它的理解范围(无论如何都会在此过程中稍后解释)。跨度>
【解决方案2】:

这是一个一元 + 运算符。它产生其操作数的值,但它只能应用于算术类型。这里的目的是防止在指针类型的表达式上使用INT8_C

但是你的语句表达

INT8_C(val);

具有未定义的行为。 INT8_C() 的参数必须是“一个无后缀的整数常量……其值不超过相应类型的限制”(N1256 7.18.4)。这些宏的目的是让您编写类似INT64_C(42) 的内容,如果int64_tlong,则将其扩展为42L,或者如果int64_t 是`long long,则扩展为42LL,等等。

但是写两个

((int8_t) + (78));

((int8_t) + (val));

在您自己的代码中是完全合法的。

编辑

问题中INT8_C()的定义:

#define INT8_C(val) ((int8_t) + (val))

在 C99 中有效,但根据后来的 N1256 草案不符合,由于技术勘误之一的更改。

原始 C99 标准第 7.18.4.1p2 节说:

INT***N*_C**(value) 应扩展为带符号的整数常量 指定的值和类型 int_least***N*_t**。

N1256 将其更改为(第 3 段):

这些宏之一的每次调用都应扩展为一个整数 适合在 #if 预处理指令中使用的常量表达式。 表达式的类型应与 对应类型转换的表达式 整数促销。表达式的值应为 论据。

更改是为了响应Defect Report #209

EDIT2:但请参阅 R.. 的回答;它实际上在 N1256 中有效,原因很模糊。

【讨论】:

  • 不仅 OP 的代码错误,而且 OP 的 stdint.h 副本也是错误的(如果这真的是他对 INT8_C 的定义)。
  • 它违反了 7.18.4 第 3 段。请参阅下面的答案,这样我就不会到处乱发垃圾邮件了。
  • 最好阅读@R.. 的答案,它可以解决这个难题。
【解决方案3】:

这是一元加号。

可以做的只有两件事。

  1. 它将所谓的常用算术转换应用于操作数。这为结果建立了一个通用的真实类型。如果结果即将被转换成特定的东西,我想不出任何理由来强制这样做。

  2. 它将操作数限制为定义算术运算符的类型。这似乎是更可能的原因。

【讨论】:

    【解决方案4】:

    似乎每个人都忽略了这里的一元加号运算符的要点,即使结果在#if 预处理器指令中有效。给定:

    #define INT8_C(val) ((int8_t) + (val))
    

    指令:

    #define FOO 1
    #if FOO == INT8_C(1)
    

    展开为:

    #if 1 == ((0) + (1))
    

    因此按预期工作。这是因为 #if 指令中的任何 un#defined 符号都会扩展为 0

    没有一元加号(在#if 指令中变成二元加号),表达式在#if 指令中无效。

    【讨论】:

    • 这真的很奇怪,不幸的是有效。这算作混淆的 C 代码吗?我认为让+ 在一种情况下是一元运算符,而在另一种情况下是二元运算符,依赖于(int8_t) 被解释为强制转换或未定义符号,这接近于混淆......
    • 既漂亮又丑陋。