【问题标题】:Why is commenting out multiline comments in c++ inconsistent?为什么在 C++ 中注释掉多行注释不一致?
【发布时间】:2015-11-18 08:17:07
【问题描述】:

所以我们知道

// This doesn't affect anything

/*
This doesn't affect anything either
*/

/*
/* /* /*
This doesn't affect anything
*/
This does because comments aren't recursive

/* /*
This doesn't affect anything
*/ */
This throws an error because the second * / is unmatched since comments aren't recursive

我听说他们不递归的原因是因为他们会slow down the compiler,我想这是有道理的。但是现在当我用更高级的语言(比如 Python)解析 c++ 代码时,我可以简单地使用正则表达式

"\/[\/]+((?![\n])[\s\S])*\r*\n"

匹配// single line comments,并使用

"\/\*((?!\*\/)[\s\S])*\*\/"

匹配/* multiline comments */,然后遍历所有单行 cmets,删除它们,然后遍历所有多行 cmets 并删除它们。或相反亦然。但这就是我卡住的地方。似乎只做其中一个是不够的,因为:

// /*
An error is thrown because the /* is ignored
*/

/*
This doesn't affect things because of mysterious reasons
// */

/*
This throws an error because the second * / is unmatched
// */ */

这种行为的原因是什么?它也是编译器解析事物方式的产物吗?明确地说,我不想改变 c++ 的行为,我只想知道第二组示例的行为背后的原因。

编辑:

所以是的,更明确地说,我的问题是为什么以下三种(看似合理的)解释这种行为的方式不起作用:

  1. 不管是 /* 还是 * /,只要忽略 // 之后的所有字符,即使您在多行注释中。

  2. 允许 / * 或 */ 后跟 // 仍然有效。

  3. 以上两者。

我理解为什么不允许嵌套 cmets,因为它们需要堆栈和任意大量的内存。但这三种情况不会。

再次编辑:

如果有人感兴趣,这里是下面的代码,按照此处讨论的正确注释规则,在 python 中提取 c/c++ 文件的 cmets:

import re
commentScanner = re.Scanner([
  (r"\/[\/]+((?![\n])[\s\S])*\r*(\n{1})?", lambda scanner, token: ("//", token)),
  (r"\/\*((?!\*\/)[\s\S])*\*\/", lambda scanner, token: ("/* ... */", token)),
  (r"[\s\S]", lambda scanner, token: None)
])
commentScanner.scan("fds a45fsa//kjl fds4325lkjfa/*jfds/\nk\lj\/*4532jlfds5342a  l/*a/*b/*c\n//fdsafa\n\r\n/*jfd//a*/fd// fs54fdsa3\r\r//\r/*\r\n2a\n\n\nois")

【问题讨论】:

  • 您说过“它不会那么慢”,但没有说明为什么应该更改语言以允许嵌套 cmets。如果你能提供一个,我可以告诉你为什么它不如让事情保持原样重要。
  • 好的,我同意,但这不是重点。
  • 好吧,现在我很困惑。您是在问为什么我们在 C++ 中没有嵌套的 cmets,或者如果我们没有,如何解析 C++? (这比我们这样做更容易...)
  • @DanielleEnsign // 不能注释掉 */,因为 ///* 注释掉了。
  • #if 0 / #endif 会很好地嵌套。

标签: c++ regex compiler-construction comments


【解决方案1】:

这不是不一致的。现有的行为既易于指定又易于实现,并且您的编译器正在正确实现它。请参阅标准中的 [lex.comment]。

字符/* 开始注释,注释以字符*/ 结束。这些 cmets 不 巢。字符// 开始一个注释,它以下一个换行符结束。如果有一个 此类注释中的换页符或垂直制表符,其间仅应出现空白字符 以及终止注释的换行符;不需要诊断。 [注: 注释字符 ///**/// 注释中没有特殊含义,它们的处理方式与其他字符一样。 同样,注释字符///*/* 注释中没有特殊含义。 — 尾注]

如您所见,// 可用于注释掉/**/。只是 cmets 没有嵌套,所以如果 // 已经在 /* 内部,那么 // 根本没有效果。

【讨论】:

  • 所以 // 不能用于注释掉 * /,否则倒数第三个示例会导致编译器错误(它不会)。但是,如果它没有匹配的东西,它会注释掉它,但关键是它仍然阻止 /* 与进一步的 */ 匹配,就像你期望它做的那样(因为 */ 在它被注释时不应该做任何事情)
  • @DanielleEnsign // 确实注释掉了 */。例如,如果您在文件开头有// */,则不会出现错误。但正如我所说,// 如果它出现在评论本身中,它就没有任何效果(它不会被解释为评论标记)。
  • @DanielleEnsign 它可以注释掉*/,除非// 已经在/* 内(即// 被注释掉并且没有影响)。
  • 好吧,很酷,这句话需要花点时间才能理解,因为它没有提供示例来明确其含义,但它确实完全描述了正确的行为,因此一旦写出看似边缘情况,他们可以将其应用于他们并理解正确的行为。谢谢:)
【解决方案2】:

是的,就像评论中的所有内容都只是文本一样,但是当您删除评论分隔符时,
暴露的文本可以再次被解析。
因此,如果该文本的一部分具有注释分隔符文字,它们将成为可解析为新的注释 delimiter

而且它始终是先到先得的问题,即从左到右的顺序。

认为解析 cmets 过于简单可能有点过于简单。
事实是必须同时解析引号(单/双),并且首先遇到的任何内容 cmets/quote,首先提供。

最后,评论中的所有内容都被跳过意味着如果您删除外部
评论层,所有剩余的不是有效评论都将被解析为
语言的一部分。这意味着无法确定任何公开的评论格式,
并且出现解析错误的机会很大,如果不是不可避免的话。

我也相信 C++ 也有 // 样式 cmets 的续行形式。
例如:

// single line continuation\
continuation               \  
end here 
code

所以用正则表达式解析 C++ cmets 的公式是你有
解析(匹配)文件中的每个字符。
如果你直接去 cmets 它将匹配注入到
错误的地方。

解析 cmets 的一个好的正则表达式如下。我最初是从 Perl 小组中得到的
并对单行注释和续行稍作修改。
有了它,您可以删除 cmets 或只查找 cmets。

原始正则表达式:

   # (/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//(?:[^\\]|\\\n?)*?\n)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|[\S\s][^/"'\\]*)


   (                                # (1 start), Comments 
        /\*                              # Start /* .. */ comment
        [^*]* \*+
        (?: [^/*] [^*]* \*+ )*
        /                                # End /* .. */ comment
     |  
        //                               # Start // comment
        (?: [^\\] | \\ \n? )*?           # Possible line-continuation
        \n                               # End // comment
   )                                # (1 end)
|  
   (                                # (2 start), Non - comments 
        "
        (?: \\ [\S\s] | [^"\\] )*        # Double quoted text
        "
     |  '
        (?: \\ [\S\s] | [^'\\] )*        # Single quoted text
        ' 
     |  [\S\s]                           # Any other char
        [^/"'\\]*                        # Chars which doesn't start a comment, string, escape,
                                         # or line continuation (escape + newline)
   )                                # (2 end)

增强(保留格式),主要用于删除 cmets。
使用多行模式:

   # ((?:(?:^[ \t]*)?(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|/\*|//)))?|//(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|/\*|//))|(?=\r?\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|(?:\r?\n|[\S\s])[^/"'\\\s]*)

   (                                # (1 start), Comments 
        (?:
             (?: ^ [ \t]* )?                  # <- To preserve formatting
             (?:
                  /\*                              # Start /* .. */ comment
                  [^*]* \*+
                  (?: [^/*] [^*]* \*+ )*
                  /                                # End /* .. */ comment
                  (?:                              # <- To preserve formatting 
                       [ \t]* \r? \n                                      
                       (?=
                            [ \t]*                  
                            (?: \r? \n | /\* | // )
                       )
                  )?
               |  
                  //                               # Start // comment
                  (?:                              # Possible line-continuation
                       [^\\] 
                    |  \\ 
                       (?: \r? \n )?
                  )*?
                  (?:                              # End // comment
                       \r? \n                               
                       (?=                              # <- To preserve formatting
                            [ \t]*                          
                            (?: \r? \n | /\* | // )
                       )
                    |  (?= \r? \n )
                  )
             )
        )+                               # Grab multiple comment blocks if need be
   )                                # (1 end)

|                                 ## OR

   (                                # (2 start), Non - comments 
        "
        (?: \\ [\S\s] | [^"\\] )*        # Double quoted text
        "
     |  '
        (?: \\ [\S\s] | [^'\\] )*        # Single quoted text
        ' 
     |  (?: \r? \n | [\S\s] )            # Linebreak or Any other char
        [^/"'\\\s]*                      # Chars which doesn't start a comment, string, escape,
                                         # or line continuation (escape + newline)
   )                                # (2 end)

【讨论】:

    【解决方案3】:

    当评论开始时,直到评论结束的所有内容都被视为评论。

    所以zero // one */ two 本身可以让zero // one */ 成为前一行中/* */ 注释的结尾,注释之外的两个two,或者它可以是一个以@ 开头的新单行注释987654325@,zero 在评论外。

    从理论上讲,// 不是有效的 C 标记或标记序列。因此,在 C 中的注释或字符串之外没有带有// 的程序。

    但是,评论中的// 是合法的。所以一个头文件包含:

    /* this is a C style comment
    // with some cool
    // slashes */
    

    如果我们将// 注释掉尾随的*/,将会中断。在 /* */ 评论中,// 被忽略。不能无缘无故地破坏与 C 的兼容性。

    // 中,所有内容都会被忽略,直到行尾。没有偷偷摸摸的/* 或允许。

    解析规则真的很简单——开始注释、啜饮和丢弃,直到看到结束标记(换行符或*/ 取决于),然后继续解析。

    由于 C++ 并非设计为通过正则表达式解析,因此您使用正则表达式解析它的困难要么没有被考虑,要么被认为不重要。

    【讨论】:

      猜你喜欢
      • 2015-12-23
      • 2013-04-28
      • 2011-02-02
      • 2013-10-05
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      • 1970-01-01
      • 2014-09-29
      相关资源
      最近更新 更多