【问题标题】:complexity of parsing C++解析 C++ 的复杂性
【发布时间】:2010-11-13 11:44:28
【问题描述】:

出于好奇,我想知道解析 C++ 的一些“理论”结果是什么。

让 n 是我的项目的大小(例如,在 LOC 中,但由于我们将处理 big-O,所以它不是很重要)

  • C++ 是否在 O(n) 中解析?如果不是,复杂程度如何?
  • C(或 Java 或其语法意义上的任何更简单的语言)是否在 O(n) 中解析?
  • C++1x 会引入更难解析的新特性吗?

参考将不胜感激!

【问题讨论】:

  • Re C++1x:当然。只是一些库功能大量使用了模板元编程,众所周知,这会导致编译时间过长。
  • 我认为 C 有一些 O(N)(或者几乎是 O(N))解析器,但肯定不是 C++。我不认为 C++ 解析速度可以用 /any/ Big-O 表示法来近似,除了 O(N^frand()) 之外,其中 'frand' 是一个无法预测的浮点随机值。
  • @sbi:不确定它是“确定的”。编译器只需要支持模板/constexpr 递归到某个实现定义的值,这意味着无论你调用什么模板,它最多只能添加有限数量的表达式和语句。 O(N+1000) 仍然只是 O(N)
  • @Damon:再读一遍。我写了 "Re C++1x: 肯定的。"这是从 2010 年 11 月开始的,所以 C++1x 指的是后来的 C++11,它引入了 lambda 函数,>> 用于关闭嵌套模板参数列表和其他肯定使 C++ 更难解析的东西。

标签: c++ parsing theory big-o compiler-theory


【解决方案1】:

我认为出于问题的目的,“解析”一词被不同的人以不同的方式解释。

在狭义的技术意义上,解析只是验证源代码是否与语法匹配(甚至可能构建一棵树)。

有一个相当普遍的民间定理说您根本无法解析 C++(在这个意义上),因为您必须解析某些符号的含义才能解析。那个民间定理是完全错误的。

这是由于使用了“弱”(LALR 或回溯递归下降)解析器,如果它们对文本的局部模糊部分(此 SO thread discusses an example)进行了几个可能的子解析的错误选择,则会完全失败由于有时会做出这样的选择。使用这种解析器解决困境的方法是在解析发生时收集符号表数据,并将额外的检查混搭到解析过程中,以强制解析器在遇到此类选择时做出正确的选择。这样做的代价是名称和类型解析与解析显着纠缠不清,这使得构建这样的解析器非常困难。但是,至少对于传统的 GCC,他们使用 LALR,它是解析的线性时间,如果你包含解析器所做的名称/类型捕获,我认为不会更昂贵(解析后还有更多工作要做,因为我没有'不认为他们什么都做)。

至少有两种使用“纯”GLR parsing 技术完成的 C++ 解析器实现,它简单地承认解析可能在本地不明确,并在没有注释或显着开销的情况下捕获多个解析。 GLR 解析器是没有局部歧义的线性时间。它们在歧义区域更昂贵,但实际上,标准 C++ 程序中的大多数源文本都属于“线性时间”部分。所以有效率是线性的,甚至可以捕捉到歧义。两个实现的解析器都在解析后进行名称和类型解析,并使用不一致来消除不正确的歧义解析。 (这两个实现是Elsaour (SD's) C++ Front End。我不能说Elsa 目前的能力(我认为它已经多年没有更新了),但我们的C++ 11 都是我们的[2015 年1 月编辑:现在完整的 C++14 编辑 2017 年 10 月:现在完整的 C++17],包括 GCC 和 Microsoft 变体)。

如果您采用严格的计算机科学定义,即语言被扩展定义为任意字符串集(语法应该是简洁地对其进行编码的方式)并将解析视为“检查程序的语法是否正确" 然后对于 C++,您已经扩展了模板以验证每个模板实际上都完全扩展了。模板中隐藏着一个图灵机,因此理论上检查 C++ 程序是否有效是不可能的(没有时间限制)。真正的编译器(遵守标准)对模板展开的数量设置了固定的约束,实际内存也是如此,因此在实践中 C++ 编译器完成了。但是从这个意义上说,他们可以花费任意长的时间来“解析”一个程序。我认为这是大多数人关心的答案。

实际上,我猜大多数模板实际上都非常简单,因此 C++ 编译器的平均速度可以与其他编译器一样快。只有疯狂到在模板中编写图灵机的人才会付出沉重的代价。 (意见:价格实际上是把复杂的东西硬塞到模板上的概念成本,而不是编译器执行成本。)

【讨论】:

  • 这正是我想知道的。谢谢。
  • bool b = (A<B>::C == D<E>::F);bool b = (A< B>::C == D<E >::F);。 IIRC 有两个解析树,它们都有有效的减速。如果有的话,哪个是可行的需要语义信息。
  • @BCS:GLR 在解析过程中获取两种解释。解析后解决哪个保留。我同意,您需要语义信息来决定。即使存在歧义,语义信息也更容易在完整的解析树上收集。
  • 很有趣。此时,您可以争论何时完成解析:在解析步骤之前或之后。该分辨率几乎肯定是 NP(SAT?)。
【解决方案2】:

很难判断 C++ 是否可以“仅解析”,因为 - 与大多数语言相反 - 如果不同时执行语义分析,就无法对其进行句法分析。

【讨论】:

  • 这不是真的。看我的回答。
【解决方案3】:

取决于您所说的“已解析”是什么意思,但如果您的解析应该包括模板实例化,那么通常不会:

[如果您想避免阅读示例的快捷方式 - 模板提供了足够丰富的计算模型,因此实例化它们通常是一个暂停式问题]

template<int N>
struct Triangle {
    static const int value = Triangle<N-1>::value + N;
};

template<>
struct Triangle<0> {
    static const int value = 0;
};

int main() {
    return Triangle<127>::value;
}

显然,在这种情况下,编译器理论上可以发现三角形数有一个简单的生成器函数,并使用它计算返回值。但除此之外,实例化 Triangle&lt;k&gt; 将花费 O(k) 时间,并且显然 k 可以随着这个程序的大小而迅速上升,直到 int 类型的限制。

[快捷方式结束]

现在,为了知道Triangle&lt;127&gt;::value 是对象还是类型,编译器实际上必须实例化Triangle&lt;127&gt;(同样,在这种情况下它可能会走捷径,因为value 被定义为对象在每个模板专业化中,但不是在一般情况下)。符号代表对象还是类型与 C++ 的语法有关,所以我可能会争辩说“解析”C++ 确实 需要模板实例化。不过,其他定义可能会有所不同。

实际实现任意限制模板实例化的深度,使大 O 分析无关紧要,但我忽略了这一点,因为无论如何实际实现都有自然资源限制,也使大 O 分析无关紧要...

我希望您可以使用递归 #include 在 C 中生成类似困难的程序,尽管我不确定您是否打算将预处理器作为解析步骤的一部分。

除此之外,C 与许多其他语言一样,可以进行 O(不是很多)解析。您可能需要符号查找等,正如大卫在他的回答中所说,一般情况下不能有严格的 O(1) 最坏情况界限,所以 O(n) 可能有点乐观。即使是汇编程序也可能会查找标签的符号。但是例如动态语言甚至不一定需要符号查找来进行解析,因为这可能在运行时完成。如果你选择一种语言,解析器需要做的就是确定哪些符号是关键字,并进行某种括号匹配,那么 Shutting Yard 算法是 O(n),所以有希望。

【讨论】:

    猜你喜欢
    • 2017-04-23
    • 1970-01-01
    • 2021-11-17
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多