词法分析器正确输出但指针 "filename" 包含错误名称

lexical analyser correct output but pointer "filename" contains wrong name

此代码的目的是读取以下文本 (d.txt,e.txt,f.txt) 并执行所需的操作以便将字母与正确顺序进入output.txt。该代码应该可以工作,因为在 output.txt 中我得到了正确的结果,但是我使用 printf 进行的测试存在问题(它在 newfile 函数的末尾)。为了 运行 我给出了输入 d.txt 和 output.txt。 它应该打印

top->prev points to file :d
top->prev points to file :e

但它打印了以下内容,我找不到原因

top->prev points to file :d
top->prev points to file :f

d.txt:

abc
#include e.txt
mno

e.txt:

def
#include f.txt
jkl

f.txt:

ghi

代码:

%{
#include <stdio.h>
#include <stdlib.h>

struct yyfilebuffer{
    YY_BUFFER_STATE bs;
    struct yyfilebuffer *prev;
    FILE *f;
    char *filename;
}*top;

int i;
char temporal[7];
void newfile(char *filename);
void popfile();
void create();
%}

%s INC
%option noyywrap
%%
"#include " {BEGIN INC;}
<INC>.*$ {for(i=1;i<strlen(yytext)-2;i++)
          {
            temporal[i-1]=yytext[i];
          }
          newfile(temporal);
          BEGIN INITIAL;
         }

<<EOF>> {popfile();
        BEGIN INITIAL;
        }
%%

void main(int argc,int **argv)
{
    if ( argc < 3 )
    {
        printf("\nUsage yybuferstate <filenamein> <filenameout>");
        exit(1);
    }
    else
    {
        create();
        newfile(argv[1]);
        yyout = fopen(argv[2], "w");
        yylex();
    }
    system("pause");
}

void create()
{
    top = NULL;
}

void newfile(char *filename)
{
    struct yyfilebuffer *newptr;
    if(top == NULL)
    {
        newptr = malloc(1*sizeof(struct yyfilebuffer));
        newptr->prev = NULL;
        newptr->filename = filename;
        newptr->f = fopen(filename,"r");
        newptr->bs = yy_create_buffer(newptr->f, YY_BUF_SIZE);
        top = newptr;
        yy_switch_to_buffer(top->bs);
    }
    else
    {
        newptr = malloc(1*sizeof(struct yyfilebuffer));
        newptr->prev = top;
        newptr->filename = filename;
        newptr->f = fopen(filename,"r");
        newptr->bs = yy_create_buffer(newptr->f, YY_BUF_SIZE);
        top = newptr;
        yy_switch_to_buffer(top->bs);   //edw
    }
    if(top->prev != NULL)
    {
        printf("top->prev points to file : %s\n",top->prev->filename);
    }
}

void popfile()
{
    struct yyfilebuffer *temp;      
    temp = NULL;
    if(top->prev == NULL)
    {
        printf("\n Error : Trying to pop from empty stack");
        exit(1);
    }
    else
    {
        temp = top;
        top = temp->prev;
        yy_switch_to_buffer(top->bs);
        system("pause");
    }
}

您需要考虑如何管理内存,记住 C 并不像其他语言那样真正具有字符串类型。

你定义了一个全局变量:

char temporal[7];

(它有一个奇怪的名字,因为全局变量不是临时的),然后在你的词法分析器中填写它的值:

for(i=1;i<strlen(yytext)-2;i++) {
        temporal[i-1]=yytext[i];
}

以上代码至少存在三个问题:

  1. temporal 只能容纳六个字符的文件名,但您无处检查以确保 yyleng 不大于 6。如果是,您将覆盖随机内存。 (flex 生成的扫描器将 yyleng 设置为起始地址为 yytext 的令牌的长度。因此您不妨使用该值而不是计算 strlen(yytext),这涉及扫描正文。)

  2. 你永远不会以 null 终止 temporal。第一次没问题,因为它有静态生命周期,因此会在程序初始化时用零填充。但是第二次和以后你指望新文件名不会比前一个短;否则,您最终会在新名称的末尾使用旧名称的一部分。

  3. 您本可以更好地利用标准 C 库。尽管出于我将在下面指出的原因,这并不能解决您观察到的问题,但最好使用以下而不是循环,在检查 之后 yyleng 是不太大:

    memcpy(temporal, yytext + 1, yyleng - 2); /* Copy the filename */
    temporal[yyleng - 2] = '[=12=]';              /* NUL-terminate the copy */
    

temporal 中制作副本后,将其交给 newfile:

newfile(temporal);

而在newfile中,我们看到的是:

newptr->filename = filename;

不复制文件名。对 newfile 的调用将 temporal 的地址作为参数传递,因此在 newfile 中,参数 filename 的值是 temporal 的地址。然后将该地址存储在 newptr->filename 中,因此 newptr->filename 也是 temporal 的地址。

但是,如上所述,temporal 不是暂时的。它是一个全局变量,其生命周期是程序的整个生命周期。所以下次你的词法扫描器遇到 include 指令时,它会将它放入 temporal,覆盖之前的内容。那么 yyfilebuffer 结构中的 filename 成员会发生什么?答:没什么。它仍然指向同一个地方,temporal,但是那个地方的内容已经改变了。因此,当您稍后打印出 filename 字段指向的字符串的内容时,您将得到一个与您第一次创建 [=39] 时碰巧在 temporal 中的字符串不同的字符串=]结构。

总的来说,如果newfilepopfile "own"文件缓冲区堆栈中的内存,您会发现管理内存更容易。这意味着 newfile 应该将其参数复制到新分配的存储中,并且 popfile 应该释放该存储,因为它不再需要了。如果 newfile 进行了复制,那么调用 newfile 的词法扫描器动作就没有必要进行复制;它只需要在调用 newfile.

时确保字符串正确以 NUL 结尾

简而言之,代码可能如下所示:

/* Changed parameter to const, since we are not modifying its contents */
void newfile(const char *filename) { 
    /* Eliminated this check as obviously unnecessary: if(top == NULL) */
    struct yyfilebuffer *newptr = malloc(sizeof(struct yyfilebuffer));
    newptr->prev = top;
    // Here we copy filename. Since I suspect that you are on Windows,
    // I'll write it out in full. Normally, I'd use strdup.
    newptr->filename = malloc(strlen(filename) + 1);
    strcpy(newptr->filename, filename);
    newptr->f = fopen(filename,"r");
    newptr->bs = yy_create_buffer(newptr->f, YY_BUF_SIZE);
    top = newptr;
    yy_switch_to_buffer(top->bs);   //edw

    if(top->prev != NULL) {
        printf("top->prev points to file : %s\n",top->prev->filename);
    }
}

void popfile() {
    if(top->prev == NULL) {
        fprintf(stderr, "Error : Trying to pop from empty stack\n");
        exit(1);
    }
    struct yyfilebuffer temp = top;
    top = temp->prev;
    /* Reclaim memory */
    free(temp->filename);
    free(temp);

    yy_switch_to_buffer(top->bs);
    system("pause");
}

现在 newfile 获得了传递给它的字符串的所有权,我们不再需要复制。由于该操作清楚地表明您希望 #include 的参数类似于 C #include 指令(被 "..."<...> 包围),因此最好明确说明:

<INC>\".+\"$|"<".+">"$ {
           /* NUL-terminate the filename by overwriting the trailing "*/
           yytext[yyleng - 1] = '[=16=]';
           newfile(yytext + 1);
           BEGIN INITIAL;
         }