我经常听到有人说c++是一种上下文敏感的语言。举个例子:

a b(c);

这是变量定义还是函数声明?这取决于符号c的含义。如果c是一个变量,则ab (c);定义一个名为b的类型为a的变量。它直接用c初始化。但如果c是类型,则ab (c);声明一个名为b的函数,该函数接受c并返回a。

如果您查找上下文无关语言的定义,它基本上会告诉您,所有语法规则的左侧必须恰好包含一个非终结符。另一方面,上下文敏感语法允许在左侧使用任意的终结符和非终结符字符串。

浏览“c++程序设计语言”的附录A,我找不到一条语法规则,它的左边除了一个非终结符之外,还有其他任何东西。这意味着c++是上下文无关的。(当然,每一种与上下文无关的语言也是与上下文相关的,因为与上下文无关的语言构成了与上下文相关的语言的一个子集,但这不是重点。)

那么,c++是上下文无关的还是上下文敏感的?


当前回答

它是上下文敏感的,如a b(c);有两个有效的解析-声明和变量。当你说“If c是一个类型”时,这就是上下文,就在那里,你已经准确地描述了c++对它的敏感程度。如果没有“c是什么?”的上下文,就无法明确地解析它。

在这里,上下文通过标记的选择来表示——如果标识符指定了类型,解析器将其读取为typename标记。这是最简单的解决方案,避免了上下文敏感的复杂性(在本例中)。

编辑:当然,还有更多的上下文敏感性问题,我只是关注你展示的那个。模板在这方面尤其糟糕。

其他回答

真正的:)

斯坦利·沃福德。计算机系统。页341 - 346。

下面是我(目前)最喜欢的关于为什么解析c++(可能)是图灵完备的演示,因为它显示了当且仅当给定整数是素数时语法正确的程序。

因此,我断言c++既不是上下文无关的,也不是上下文敏感的。

If you allow arbitrary symbol sequences on both sides of any production, you produce an Type-0 grammar ("unrestricted") in the Chomsky hierarchy, which is more powerful than a context-sensitive grammar; unrestricted grammars are Turing-complete. A context-sensitive (Type-1) grammar allows multiple symbols of context on the left hand side of a production, but the same context must appear on the right hand side of the production (hence the name "context-sensitive"). [1] Context-sensitive grammars are equivalent to linear-bounded Turing machines.

在示例程序中,质数计算可以由线性有界的图灵机执行,因此它不能完全证明图灵等价,但重要的部分是解析器需要执行计算以便执行语法分析。它可以是任何可以表示为模板实例化的计算,并且有充分的理由相信c++模板实例化是图灵完备的。例如,Todd L. Veldhuizen在2003年的论文。

不管怎样,c++可以被计算机解析,所以它当然可以被图灵机解析。因此,不受限制的语法可以识别它。实际上,编写这样的语法是不切实际的,这就是标准没有尝试这样做的原因。(见下文)。

某些表达的“模糊性”问题主要是转移注意力。首先,歧义是一种特定语法的特征,而不是一种语言。即使一种语言可以被证明没有明确的语法,如果它可以被上下文无关的语法识别,那么它就是上下文无关的。类似地,如果它不能被上下文无关的语法识别,但可以被上下文敏感的语法识别,那么它就是上下文敏感的。模棱两可无关紧要。

But in any event, like line 21 (i.e. auto b = foo<IsPrime<234799>>::typen<1>();) in the program below, the expressions are not ambiguous at all; they are simply parsed differently depending on context. In the simplest expression of the issue, the syntactic category of certain identifiers is dependent on how they have been declared (types and functions, for example), which means that the formal language would have to recognize the fact that two arbitrary-length strings in the same program are identical (declaration and use). This can be modelled by the "copy" grammar, which is the grammar which recognizes two consecutive exact copies of the same word. It's easy to prove with the pumping lemma that this language is not context-free. A context-sensitive grammar for this language is possible, and a Type-0 grammar is provided in the answer to this question: https://math.stackexchange.com/questions/163830/context-sensitive-grammar-for-the-copy-language .

如果有人试图编写上下文敏感的(或不受限制的)语法来解析c++,那么很可能会到处乱写乱写。编写图灵机来解析c++同样是不可能完成的任务。即使是编写c++程序也是困难的,而且据我所知,没有一个被证明是正确的。这就是为什么标准没有尝试提供完整的正式语法,以及为什么它选择用技术英语编写一些解析规则。

What looks like a formal grammar in the C++ standard is not the complete formal definition of the syntax of the C++ language. It's not even the complete formal definition of the language after preprocessing, which might be easier to formalize. (That wouldn't be the language, though: the C++ language as defined by the standard includes the preprocessor, and the operation of the preprocessor is described algorithmically since it would be extremely hard to describe in any grammatical formalism. It is in that section of the standard where lexical decomposition is described, including the rules where it must be applied more than once.)

各种语法(两个用于词法分析的重叠语法,一个发生在预处理之前,另一个,如果有必要,发生在预处理之后,再加上“句法”语法)收集在附录A中,其中有一个重要的注释(强调):

本文对c++语法的总结旨在帮助理解。这不是对语言的准确表述。特别地,这里描述的语法接受有效的c++构造的超集。必须应用消歧规则(6.8,7.1,10.2)来区分表达式和声明。此外,必须使用访问控制、模糊性和类型规则来清除语法上有效但无意义的结构。

最后,这是我们承诺的项目。当且仅当IsPrime中的N <N>是素数时,第21行在语法上是正确的。否则,typen是一个整数,而不是一个模板,因此typen<1>()将被解析为(typen<1)>(),这在语法上是不正确的,因为()不是一个语法上有效的表达式。

template<bool V> struct answer { answer(int) {} bool operator()(){return V;}};

template<bool no, bool yes, int f, int p> struct IsPrimeHelper
  : IsPrimeHelper<p % f == 0, f * f >= p, f + 2, p> {};
template<bool yes, int f, int p> struct IsPrimeHelper<true, yes, f, p> { using type = answer<false>; };
template<int f, int p> struct IsPrimeHelper<false, true, f, p> { using type = answer<true>; };

template<int I> using IsPrime = typename IsPrimeHelper<!(I&1), false, 3, I>::type;
template<int I>
struct X { static const int i = I; int a[i]; }; 

template<typename A> struct foo;
template<>struct foo<answer<true>>{
  template<int I> using typen = X<I>;
};
template<> struct foo<answer<false>>{
  static const int typen = 0;
};

int main() {
  auto b = foo<IsPrime<234799>>::typen<1>(); // Syntax error if not prime
  return 0;
}

更专业地说,上下文敏感语法中的每个结果都必须是这样的:

aB → AGB

其中A是非终结符,α、β可能是语法符号的空序列,γ是非空序列。(语法符号可以是终结符也可以是非终结符)。

这只能在[α, β]中被理解为A→γ。在上下文无关(类型2)语法中,α和β必须为空。

事实证明,你也可以用“单调”限制语法,其中每个结果必须是这样的形式:

α→β,|α|≥|β| > 0(|α|意味着“α”的长度)

可以证明单调语法所识别的语言集与上下文敏感语法所识别的语言集完全相同,而且通常更容易将证明建立在单调语法的基础上。因此,我们经常看到“上下文敏感”被当作“单调”的意思来使用。

使用GLR解析器解析c++。这意味着在解析源代码期间,解析器可能会遇到歧义,但它应该继续并决定稍后使用哪个语法规则。

还看,

为什么c++不能用LR(1)解析器解析?


请记住,上下文无关语法不能描述编程语言语法的所有规则。例如,属性语法用于检查表达式类型的有效性。

int x;
x = 9 + 1.0;

你不能用上下文无关的语法描述下面的规则: 作业的右侧应与左侧的类型相同。

c++不是上下文无关的。我以前在编译器课上学过。快速搜索得到了这个链接,其中“语法或语义”部分解释了为什么C和c++不是上下文无关的:

维基百科演讲:上下文无关语法

眼神, Ovanes

是的,c++是上下文敏感的,非常上下文敏感。您不能通过使用上下文无关的解析器简单地解析文件来构建语法树,因为在某些情况下,您需要从以前的知识中了解符号来决定(例如。在解析时构建一个符号表)。

第一个例子:

A*B;

这是一个乘法表达式吗?

OR

这是B变量作为a类型指针的声明吗?

如果A是一个变量,那么它就是一个表达式,如果A是类型,它就是一个指针声明。

第二个例子:

A B(bar);

这是一个函数原型接受一个条形参数吗?

OR

这是声明A类型的变量B,并调用A的构造函数和bar常量作为初始化式吗?

您需要再次了解bar是符号表中的变量还是类型。

第三个例子:

class Foo
{
public:
    void fn(){x*y;}
    int x, y;
};

当解析时构建符号表没有帮助时,就是这种情况,因为x和y的声明在函数定义之后。因此,您需要首先浏览类定义,然后在第二步查看方法定义,以确定x*y是一个表达式,而不是指针声明或其他东西。