【问题标题】:C - How to find all inner loops using grep?C - 如何使用 grep 查找所有内部循环?
【发布时间】:2011-11-25 14:16:05
【问题描述】:

我有一个包含大量 C 文件的大型 C 项目。我必须找到所有的内循环。我确信项目中没有任何 O(n³) 块,因此必须找到 O(n²)-compexity 块(循环中的循环)。

是否可以使用 grep 找到所有内部循环?如果是,我可以使用什么正则表达式来查找所有出现的各种内部循环,例如 {for,for}、{while,for}、{for、while}、{do、while} 等?如果没有,是否有任何简单的 unix-way 方法来做到这一点(可能是多个 grep 或一种 awk)?

【问题讨论】:

  • I am sure there is no O(n^3) block ... 真的吗?你知道你正在调用的每个函数的复杂性吗(尤其是在循环中)?
  • 识别“带有循环的循环”显然是不够的。你需要分析一切(for (;i<= n*n*n*n;)?while(1)?goto?)
  • 为什么不使用分析器?
  • @Alex:别忘了setjmp/longjmp ...
  • 禁止不代表缺席。

标签: c regex grep code-analysis


【解决方案1】:

正则表达式适用于正则语言,您所描述的似乎是无上下文的,我很确定它不能使用正则表达式来完成。请参阅类似问题的答案here。您应该寻找其他类型的自动机,例如脚本语言(python 左右)。

【讨论】:

  • 为什么?假设要查找所有以 for 开头并在其 {} 正文中包含另一个 for 的块。 {while,for}、{for,while} 等的规则相同。正则表达式可以吗?当然。
  • @psihodelia 您不能使用正则表达式来跟踪匹配大括号之类的内容,因此虽然您可以在另一个 for 之后查找for,但您不能说它是否在第一个for 与否。
  • 我知道你想要什么。但是你会发现只有单词“while”后面跟着大括号和另一个“while”,不可能选择代表你正在寻找的东西的内部文本块。正则表达式是模式匹配机器,你需要一个更高阶的自动机在其中加入一些逻辑。
  • @6D65 我只需要在 for*{*for 模式中找到第一个 for 的行号。
  • @psihodelia 您可以尝试使用grep 'for([^,]*,[^,]*,[^)]*){[^}]*for' 之类的方法,但在很多情况下都会失败,而且您也无法做得更好,因为正则表达式无法匹配括号或大括号,所以如果您在嵌套中有一个 if{...},您无法将 } 与您的 for(...){...} 的结尾括号区分开来。
【解决方案2】:

这对于特定的编译器扩展来说是一个很好的例子。 最新的 GCC 编译器(即版本 4.6 的 GCC)可以通过 插件 扩展(痛苦地用 C 编码) 或通过MELT 扩展; MELT 是一种用于编写 GCC 扩展的高级域特定语言,MELT 比 C 更易于使用。

但是,我承认编写 GCC 扩展代码并非完全简单:您必须部分了解 GCC 的工作原理,以及它的主要内部表示形式(Gimple、Tree、...)。扩展 GCC 时,您基本上添加了自己的编译器通道,它可以做任何您想做的事情(包括检测嵌套循环)。编写 GCC 扩展通常需要一周以上的工作。 (最难的部分是理解 GCC 的工作原理)。

在 GCC 框架中工作(通过 C 中的插件或 MELT 中的扩展)的一大优势是您的扩展与编译器处理相同的数据。

回到寻找嵌套循环的问题上,不要认为它只是纯粹的句法(这就是grep 不能工作的原因)。在 GCC 编译器中,在某种级别的内部表示中,由for、或while、或do,甚至使用goto-s 实现的循环仍然被认为是一个循环,而对于 GCC,这些东西可以嵌套(并且 GCC 知道嵌套!)。

【讨论】:

    【解决方案3】:

    没有 C 解析器,您最多只能获得启发式解决方案。

    如果您可以依赖代码中始终遵循的某些规则(例如,没有goto,没有通过递归的循环,...),您可以使用正则表达式扫描预处理的 C 代码。当然,grep 不够复杂,但只需几行 Perl 或类似代码就可以了。

    但技术上更好、更可靠的方法是使用真正的 C 解析器。

    【讨论】:

    • 如果你想完成一项彻底的工作,你需要的不仅仅是一个 C 解析器。
    【解决方案4】:

    C 中有三种循环:

    • “结构化语法”(whilefor、...) [注意 GCC,它可以隐藏语句,因此使用 (stmt; exp) 语法在表达式内循环!]
    • 使用 goto 的临时循环;它们与结构化语法交互。
    • 递归

    要找到第一种,你必须找到结构化的语法和嵌套。

    Grep 当然可以找到关键字(如果您忽略 cmets 和字符串中的误报),但它无法找到嵌套结构。您当然可以使用 grep 查找所有循环语法,然后简单地检查同一文件中出现的那些语法以查看它们是否嵌套。 (如果你想在没有误报的情况下这样做,你可以使用我们的Source Code Search Engine,它知道 C 的词法语法,并且永远不会混淆字符串何时是关键字、数字、字符串等.)

    如果您想自动找到这些循环,您几乎需要一个完整的 C 解析器并完成扩展预处理。 (否则某些宏可能会隐藏关键的循环语法)。一旦有了 C 的语法树,就可以直接(尽管可能有点不方便)编写一些爬过树的代码,检测循环语法节点,并计算子树中的循环嵌套。您可以使用任何能够解析 C 并为您提供抽象 sytnax 树的工具来执行此操作。 ANTLR 可能会这样做;我认为 ANTLR 有一个 C 语法可以很好地处理 C,但是在使用 ANTLR 之前你必须运行预处理器。

    您也可以使用我们的DMS Software Reengineering Toolkit 及其C Front End 来执行此操作。我们的 C 前端内置了完整的预处理器,因此它可以直接读取代码并在解析时进行扩展;它还处理相对广泛的 C 方言和字符编码(曾经处理过包含日语文本的 C 吗?)。 DMS 提供了一个额外的优势:给定一种语言(例如 C)前端,您可以直接使用语言语法为该语言编写模式。所以我们可以很容易地表达我们想要找到的片段:

     pattern block_for_loop(t:expression,l:expression,i:expression, s: statements): statement
         " for(\t,\l\,\i) { \s } ";
    
     pattern statement_for_loop(t:expression,l:expression,i:expression, s: statement): statement
         " for(\t,\l\,\i)  \s ";
    
     pattern block_while_loop(c:expression, s: statements): statement
         " while(\c) { \s } ";
    
     pattern statement_while_loop(c:expression): statement
         " for(\c)  \s ";
    
     ...
    

    并将它们组合在一起:

     pattern_set syntactic_loops
         { block_for_loop,
           statement_for_loop,
           block_while_loop,
           block_statement_loop,
           ...
         }
    

    给定模式集,DMS 可以扫描语法树并找到与任何集合成员的匹配项,而无需编写任何特定的树爬行机制,也无需了解有关树结构的大量细节。 (对于真正的 C 解析器,AST 中有很多节点类型!)以这种方式查找嵌套循环非常简单:自上而下扫描树以查找循环(​​使用模式集);任何命中都必须是顶级循环。扫描找到的循环 AST 节点的子树(当您知道外循环的树在哪里时很容易)以查找其他循环;任何命中都必须是嵌套循环;如有必要,递归以拾取具有任意嵌套的循环。这也适用于 GCC loops-with-statements 的东西。树节点标有精确的文件/行/列信息,因此很容易生成位置报告。

    对于使用 goto 构建的临时循环(什么,您的代码没有?),您需要能够生成实际控制流图的东西,然后将该图构造成嵌套控件流动区域。这里的要点是,包含无条件 goto out 的 while 循环并不是真正的循环,尽管有语法。和一个 if 语句,其 then 子句返回到 if 上游的代码可能真的是一个循环。所有这些循环语法的东西实际上只是你可能有循环的启发式提示! 这些控制流区域包含控制流的真正嵌套; DMS 将construct C flow graphs, and will produce those structured regions。它提供了用于构建和访问该图的库;这样你就可以得到基于 goto 的“真正的”控制流。找到一对嵌套的控制流区域后,可以访问与部分区域关联的 AST 以获取要报告的位置信息。

    GCC 总是很有趣,因为它显着增强了 C 版本。例如,它有一个间接的 goto 语句。 (常规 C 隐藏在 setjmp/longjmp 下!)。 面对这种情况,要找出循环,您需要points-to analysis,DMS 也提供了它。该信息由嵌套区域分析使用。点分析的准确性存在(保守的)限制,但以您获得正确的嵌套区域图为模。

    递归更难找到。为此,您必须确定 A 是否调用 B ... 调用 Z 调用 A,其中 A 和 B 和 ... 可以位于单独的编译单元中。您需要一个global call graph,其中包含应用程序的所有编译单元。在这一点上,您可能希望我说 DMS 也这样做,瞧,我很高兴地说它确实。构建该调用图当然需要对函数调用进行点对点分析;是的,DMS 也这样做。使用调用图,您可以在调用图中找到可能是递归的循环。同样通过调用图,您可以找到间接嵌套,例如,一个函数中的循环,它调用另一个也包含循环的编译单元中的函数。

    要准确地找到这样的循环结构,您需要大量的机器(这需要一些努力,但 C 是一种很难分析的语言),而 DMS 可以提供它。

    如果您不关心准确性,也不关心所有类型的循环,您可以使用 grep 和手动过程来获取结构化循环语句所暗示的循环的半精确映射.

    【讨论】:

      【解决方案5】:

      我怀疑单独使用 grep 是不可能找到这样的东西的:

      public void do(){
          for(...){
              somethingElse();
          }
      }
      
      ... Insert other code...
      
      public void somethingElse(){
          for(.....){
              print(....);
          }
      }
      

      【讨论】:

      • 让我们让它变得非常有趣。将其他东西设为(函数)操作指针,然后进行间接调用。
      猜你喜欢
      • 1970-01-01
      • 2011-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-04
      • 2015-10-22
      • 2011-03-23
      • 2010-10-07
      相关资源
      最近更新 更多