当人们说C++有"不可判断的语法"时,人们的意思是什么?

jju*_*uma 34 c++ compiler-construction

人们说这话时意味着什么?对程序员和编译器有什么影响?

Jay*_*rod 57

这与C++的模板系统是图灵完备的事实有关.这意味着(理论上)您可以使用可以使用任何其他图灵完整语言或系统的模板在编译时计算任何内容.

这有副作用,一些显然有效的C++程序无法编译; 编译器永远无法决定程序是否有效.如果编译器可以决定所有程序的有效性,它将能够解决停机问题.

请注意,这与C++语法的歧义无关.


编辑: Josh Haberman在下面的评论和博客文章中指出了一个很好的例子,为C++构建一个解析树实际上是不可判定的.由于语法的模糊性,不可能将语法分析与语义分析分开,因为语义分析是不可判定的,语法分析也是如此.

另见(Josh的帖子链接):

  • @Evan,它是C++程序员的优势,因为你可以在编译时计算东西.但是,它使编写一个好的C++编译器变得更加困难. (7认同)
  • 这对我来说是新闻,但我在第一句话中完全看到了它 - 太棒了!@Evan - 是的,但是不可判断的不一定是"缺陷" - 它只是它的方式; 就像公理逻辑不是"有缺陷"只是因为它是不完整的(哥德尔). (4认同)
  • 这不是真的.允许实现拒绝程序,因为模板实例化/递归超过某个任意深度.C++ 11标准建议允许至少1024个嵌套模板实例化,但这实际上不是必需的.因此,所有模板元程序在O(1)时间内停止.类似地,C和C++具有预处理器元程序,但允许限制"#include"的(递归)嵌套级别. (4认同)
  • @Blaisorblade:有用,但就形式定义而言,标准规定了一个实现定义的限制,这有很大的不同.然而(我应该在12月记得这一点),在`operator->`的向下钻取行为中,至少有一个模板递归*没有*模板嵌套的实例.这种机制中的无限递归可能会导致除GCC之外的所有C++编译器崩溃,我在去年6月修改了它,将其视为嵌套(在标准术语中不正确).http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49118 (4认同)
  • 有趣的是,c ++中的图灵完整模板系统是我认为最大的优势之一. (3认同)
  • @Potatoswatter:将 C++ 模板视为图灵完备更有用(除非您的程序达到实例化深度,您通常甚至可以增加),就像几乎每个人对计算机所做的一样(没有无限内存,因此不是图灵完备)。编译器无法区分真正的循环程序与仅需要一个模板实例化的程序。 (2认同)
  • 这个答案是不正确的。它确实与 C++ 语法的歧义有关。问题在于,传统上由普通 C 中的“词法分析器黑客”执行的类型/变量名称消歧可能需要任意模板实例化才能在 C++ 中解决。由于模板实例化是图灵完备的,除非您限制模板实例化深度,否则在 C++ 中简单地生成 *解析树* 通常是不可判定的。 (2认同)

Dav*_*ley 12

它可能意味着C++语法在语法上是模糊的,你可以根据上下文写下一些可能意味着不同事物的代码.(语法是对语言语法的描述.它决定了这a + b是一个包含变量a和b的加法运算.)

例如,foo bar(int(x));如所写的,可以是名为bar的变量的声明,类型为foo,其中int(x)是初始化器.它也可以是一个名为bar的函数声明,取一个int,并返回一个foo.这是在语言中定义的,但不是语法的一部分.

编程语言的语法很重要.首先,它是一种理解语言的方法,其次,它是编译的一部分,可以快速完成.因此,与C++具有明确的语法相比,C++编译器更难编写并且使用起来更慢.此外,更容易制作某些类型的错误,尽管一个好的编译器将提供足够的线索.

  • 如果你把它设为'foo*bar(int(x))'那么它可以是:(a)表达式,(b)对象声明或(c)函数声明. (4认同)

Tho*_*day 9

如果"有些人"包括Yossi Kreinin,那么根据他在这里所写的内容......

考虑这个例子:

x * y(z);
Run Code Online (Sandbox Code Playgroud)

在两种不同的背景下:

int main() {
    int x, y(int), z;
    x * y(z);
}
Run Code Online (Sandbox Code Playgroud)

int main() {
    struct x { x(int) {} } *z;
    x * y(z);
}
Run Code Online (Sandbox Code Playgroud)

......他的意思是"你不能通过查看x*y(z)决定它是表达还是声明." 在第一种情况下,它表示"使用参数z调用函数y,然后使用x调用operator*(int,int)和函数调用的返回值,最后丢弃结果." 在第二种情况下,它意味着"y是指向结构x的指针,初始化为指向与z相同的(垃圾和定时炸弹)地址."

假设您有一个COBOLmania,并将DECLARE添加到该语言中.然后第二个会成为

int main() {
    DECLARE struct x { x(int) {} } *z;
    DECLARE x * y(z);
}
Run Code Online (Sandbox Code Playgroud)

并且会出现可判定性.请注意,可判定式不会使指针到垃圾问题消失.

  • 当然,这不仅限于C++ - 例如考虑BASIC - 是"x = 1"的赋值还是测试?只有在上下文中你能说出来. (9认同)
  • @anon:在 BASIC 中,知道您匹配哪个产生式“y=1”就足够了,即它是表达式还是语句。C++ 更复杂,因为相同的字符序列、相同的位置和相同的直接上下文可能意味着完全不同的东西。 (2认同)