【问题标题】:Conventions to write simple additions of hexadecimal and decimal numbers编写十六进制和十进制数的简单加法的约定
【发布时间】:2018-09-20 21:51:25
【问题描述】:

尽管是老前辈,但我担心我(不再)完全掌握 C 中常量的解析。以下 1-liners 中的第二个无法编译:

int main( void ) { return (0xe +2); }
int main( void ) { return (0xe+2); }

$ gcc -s 奇怪的.c

weird.c: In function ‘main’:
weird.c:1:28: error: invalid suffix "+2" on integer constant
int main( void ) { return (0xe+2); }
                           ^

编译失败的原因可能是 0xe+2 按照 C11 标准条款 6.4.4.2 被解析为十六进制浮点常量。我的问题是是否存在约定来用C编写简单的十六进制和十进制数的加法,我不喜欢在解析中依赖空格。

这是 gcc 版本 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)。预处理后停止编译(-E)表明编译失败发生在gcc而不是cpp。

【问题讨论】:

标签: c parsing gcc c99


【解决方案1】:

因为 GCC 认为 0xe+2 是一个浮点数,而这只是两个整数的加法。

根据cppreference

由于最大咀嚼,eE 结尾的十六进制整数常量, 后跟运算符+- 时,必须与 源中带有空格或括号的运算符

int x = 0xE+2;   // error
int y = 0xa+2;   // OK
int z = 0xE +2;  // OK
int q = (0xE)+2; // OK

【讨论】:

  • @baard:空格约定。
  • @Baard 单看第 6.4.4 章实际上无法理解编译失败的原因。
  • @Baard 空格对你做了什么?我可以看到使用空格的唯一缺点是,如果您使用的 IDE 具有错误的格式,并且您将其配置为删除运算符周围的空格并格式化文件。一个好的 IDE 不会删除必要的空间,因为在这种情况下,一个不好的 IDE 只会应用规则并导致错误(但错误会立即被识别并轻松修复,所以这没什么大不了的......)。然而,括号可能会因为同样的原因而失败:当被要求删除不必要的括号时,如果它没有考虑到这种特殊情况,那么你又被搞砸了。
  • @Lundin "0xe" 是一个有效的整数常量,没有后缀。 “0xeL”是具有有效后缀“L”的有效整数常量。将“0xe+2”再次描述为整数常量“0xe”并不是那么,而是使用invalid 后缀“+2”。不过,我同意错误消息可能会更好。
  • 是的,错误消息的“更正”版本可能是“预处理数字不是有效的整数或浮点常量”。不过,我不确定这是否更容易理解。
【解决方案2】:

我的问题是是否存在在 C 中编写简单的十六进制和十进制数字相加的约定

约定是使用空格。这实际上是 C11 6.4 §3 规定的:

预处理标记可以用空格分隔;这包括 cmets(稍后描述)或空白字符(空格、水平制表符、换行符、垂直制表符和换页符),或两者兼而有之。

普通空格是常用的空格。

语言中到处存在类似的奇异问题,一些例子:

  • ---a 必须重写为 - --a
  • a+++++b 必须改写为a++ + ++b
  • a /// comment
    b;
    必须改写为
    a / // comment
    b

等等。所有这些情况的罪魁祸首是令牌解析器,它遵循所谓的“最大咀嚼规则”,C11 6.4 §4:

如果输入流已被解析为预处理标记,直到给定字符,则 下一个预处理标记是可以构成一个最长的字符序列 预处理令牌。

在这种特定情况下,当预处理器建立一个名为 pp-number 的预处理标记时,预处理器不会区分浮点常量和整数常量,在 C11 6.4 中定义.8:

pp-number e 符号
pp-number E 符号
pp-number p 符号
pp-number P 符号
pp-number

预处理数字以一个数字开头,可选地以句点 (.) 开头,并且可以 后跟有效的标识符字符和字符序列 e+、e-、E+、E-、 p+、p-、P+ 或 P-。

这里,pp-number 显然不必是浮点常数,就预处理器而言。


( 作为旁注,在字符串中终止十六进制转义序列时也存在类似的约定。例如,如果我想在新行上打印字符串 "ABBA",那么我不能写

puts("\xD\xABBA");(CR+LF+字符串)

因为这种情况下的字符串可以被解释为十六进制转义序列的一部分。相反,我必须使用空格来结束转义序列,然后依赖预处理器字符串连接:puts("\xD\xA" "BBA")。目的是一样的,指导预处理器如何解析代码。 )

【讨论】:

  • 这似乎无法解释为什么0xE+3 可以构成预处理令牌,而0xF+3 却不能。
  • @Ruslan 不,因为这只回答了问题。原因与 6.4.8 中的最大 munch 规则和相当生硬的 pp-number 语法有关,其中标准显然在整数常量和浮点常量之间没有任何区别。这里最长的有效token序列是pp-number e sign.
  • 我也将那部分添加到答案中。
  • 另一个需要空格的“合理”代码示例:val/*ptr。如果不喜欢空格,这些东西也可以用括号固定。 -(--a) 可能被认为比 - --a 更具可读性,尽管它们都很糟糕。
  • @wrtlprnft 有很多方法可以让解析器满意,但这并不意味着它们是约定俗成的。你也可以写 val/&ptr[0]val/+*ptr;-+--a 但这是混淆。使用空格是最易读的方式。
猜你喜欢
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
  • 2022-11-16
  • 2016-04-17
  • 1970-01-01
  • 2013-06-10
  • 2018-07-26
  • 2014-10-30
相关资源
最近更新 更多