1

我试图为我的柠檬解析器使用 void* 类型,但我遇到了一些奇怪的问题。

最初我使用自定义标记类型,一个结构来保存标记的值,然后我切换到 void* 因为我的标记值类型不同。

这是我的一些解析器代码;

expression(A) ::= expression(B) PLUS expression(C). { *((double *)A)=  *((double *)B)  +  *((double *)C) ; }
expression(A) ::= expression(B) MINUS expression(C). { *((double *)A)= *((double *) B)  -  *((double *)C) ;  }
expression(A) ::= expression(B) MULT expression(C). { *((double *)A)=  *((double *)B)  *   *((double *)C) ; }
expression(A) ::= expression(B) DIV expression(C). {
        if( *((double *)C)  != 0)
                *((double *)A)=  *((double *)B)  /  *((double *)C) ;
        else
                printf("Math Error!");
}

expression(A) ::= number(B). { *((double *)A)=  *((double *)B) ;}
number ::= INT.
number ::= FLOAT.

这是我的词法分析器,它是 re2c 文件;

while ((token = lex()) != EOL) {
        sy[size].val = tkn.val;

        parse(parser, token, &sy[size].val);
        size++;
}

sy[size].val是双重类型。

但是当我运行它时1+2返回4,当我运行1+4它时返回8

我的猜测是解析器将最正确的值放入它的堆栈中,并在它看到令牌参数的任何地方使用它。

4

1 回答 1

0

这是一个简单但错误的程序:

double* add_indirect(double* b, double* c) {
  double *a;
  *a = *b + *c;    /* Undefined behaviour! */
  return a;        /* This, too! */
}

应该清楚为什么该程序是错误的:a从未被初始化。它的声明说它是一个指向 a 的指针double,但它从来没有指向任何东西。因此,当尝试通过第 3 行中的该指针存储值时,会修改随机内存——无论未初始化的指针偶然指向什么。然后,该函数返回该随机值,使用它会造成更大的破坏。

如果程序员很幸运,他们会在执行第 3 行时遇到分段错误,因为 的随机未初始化值a不是有效指针。但是很有可能从堆栈中取出的值是一个有效的指针。例如,它可能是 的值b,放置在堆栈上以便调用函数。(大多数现代编译器不会像这样使用调用堆栈,但可能会发生类似的事情。)

现在,让我们看看程序中的操作。

expression(A) ::= expression(B) PLUS expression(C). {
    *((double *)A)=  *((double *)B)  +  *((double *)C) ;
}

制作A和强制转换它们使该动作更难阅读,但可以识别出与上述失败程序中的第 3 行相同B。Lemon操作应该设置左侧非终结符的值(在本例中由 再次,分段错误将是一个幸运的结果,因为它可能会突出显示程序,但在解析器生成器的情况下,与现代编译代码不同,很可能未初始化的值恰好是某个值已经在解析器堆栈。C void*double*AAA

我看不出有什么明显的原因需要这个计算器中标记的语义值作为指向任何东西的指针。这样做会使您的代码变得相当复杂;例如,您被迫将每个标记化的值存储在一个向量中(如果输入文本太大,可能会溢出),以便它们都有唯一的地址。仅使用值类型会简单得多:

%token-type { double }
%default-type { double }

expression(A) ::= expression(B) PLUS expression(C).  { A = B + C; }
expression(A) ::= expression(B) MINUS expression(C). { A = B - C;  }
expression(A) ::= expression(B) MULT expression(C).  { A = B * C; }
expression(A) ::= expression(B) DIV expression(C).   {
        if( C != 0)
          A = B / C;
        else
          fprintf(stderr, "%s\n", "Math Error! Divide by zero.");
}

expression(A) ::= number(B). { A = B ;}

然后您的驱动程序变得简单:

while ((token = lex()) != EOL) {
        parse(parser, token, tkn.val);
}

显然,您希望这些值具有不同的类型。使值指针无法帮助您实现此目标,因为 C 中指针的实现,甚至 a void*,都是原始内存地址;它不记录任何类型信息。不可能通过查询指针来确定它恰好指向的数据类型。(因此,number无论是指向 adouble的指针还是指向 a 的指针都会int丢失有关它最初是什么的信息。)如果你想要这个功能,你的令牌类型将需要是 a union-- 如果每个令牌和非终端都有一个特定的类型 - 或您自己的通常称为“有区别的联合”的实现;即struct包含 aunion和枚举值的 a,它解释了union已验证。但在这两种情况下,值都不是指针(令牌值确实是指针的情况除外,例如字符串);语义值是令牌对象的直接值,即使该值是 a (希望很小) struct

于 2019-07-27T14:40:38.347 回答