C+YACC中变量被覆盖,为什么?

Varibles are overwriten in C+YACC, why?

我有下一个 yacc 文件:

%error-verbose
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define DEFAULT 0
#define SHOW    1
#define ASSIGN  2


char *variables[26];
int used_ids[26]={0};

char* concat(char* s1, char* s2, char* s3);
char* int_to_string(int i);
int count_digits(int n);
void action(int code, char id, char* value);

int yyerror();
int yylex();



%}

%union {
    int i;
    char c;
    struct expression{
            int code_action;
            char id;
            char* value;
    } expression;
}

%type <i> INT
%type <c> ID
%type <expression> expr

%token SUM SUB MUL DIV
%token IS
%token ID
%token INT
%token LPAR RPAR
%token EOLN


%left SUM
%left SUB
%left MUL
%left DIV

%%

expr_lst : 
      expr_lst expr EOLN    { action( .code_action, .id, .value ); }
    | expr EOLN             { action( .code_action, .id, .value ); }
    ;


expr :  ID IS expr      { $$.code_action=ASSIGN; $$.id = ; $$.value = .value; }
      | INT             { $$.code_action=DEFAULT; $$.id=DEFAULT; $$.value = int_to_string(); }
      | ID              { $$.code_action=SHOW; if(used_ids[-'a']!= 0){$$.id = ; $$.value = variables[-'a'];}else{char string[2]; string[0]=;  string[1]=0;$$.id = ; $$.value=string;}}
      | expr SUM expr   { $$.code_action=SHOW; $$.id=DEFAULT; strcpy($$.value, concat(.value,"+",.value));}
      | expr SUB expr   { $$.code_action=SHOW; $$.id=DEFAULT; strcpy($$.value, concat(.value,"-",.value));}
      | expr MUL expr   { $$.code_action=SHOW; $$.id=DEFAULT; strcpy($$.value, concat(.value,"*",.value));}
      | expr DIV expr   { $$.code_action=SHOW; $$.id=DEFAULT; strcpy($$.value, concat(.value,"/",.value));}
      | LPAR expr RPAR  { $$ = ; }
      ;



%%


int yyerror( char* m ) {
   fprintf( stderr, "%s\n", m );
}

int main() {
  return yyparse();
}

void action(int code, char id, char* value){
    /*for(int i =0; i<26;i++){
        printf("%c---%s\n", 'a'+i,variables[i]);
    }*/
    switch(code){
        case SHOW:
            printf("%s\n", value);
            break;
        case ASSIGN:
            variables[(int)id-'a'] = malloc(sizeof(char)*strlen(value));
            strcpy(variables[(int)id-'a'], value);
            used_ids[(int)id-'a'] = 1;
            break;
        default:
            break;

    }
}

char* concat(char* s1, char* s2, char* s3 ){

    char* final_string = malloc(sizeof(s1)+sizeof(s2)+sizeof(s3));

    char* ss1=malloc(sizeof(s1));
    char* ss2=malloc(sizeof(s2));
    char* ss3=malloc(sizeof(s3));
    strcpy(ss1, s1);    
    strcpy(ss2, s2);    
    strcpy(ss3, s3);    
    strcpy(final_string, ss1);  
    strcat(final_string, " ");  
    strcat(final_string, ss2);  
    strcat(final_string, " ");  
    strcat(final_string, ss3);
    return final_string;
}

char* int_to_string(int i){
    char* final_string= malloc(count_digits(i)*sizeof(char));
    sprintf(final_string, "%d", i);
    return final_string;
}

int count_digits(int n){
    int count = 0;
    while(n != 0)
    {
        n /= 10;
        ++count;
    }
}

它对应的 lex 文件:

%option noyywrap
%{
#include "interpret.tab.h"
%}

%x string
%x substring
%%

[\t ]+      /* ignore whitespace */ ;
"+"         { return SUM; }
"-"         { return SUB; }
"*"         { return MUL; }
"/"         { return DIV; }

":="        {return IS;}

"("         { return LPAR; }
")"         { return RPAR; }

[a-z]       { yylval.c = yytext[0];return ID; }

[0-9]+      { yylval.i = atoi( yytext ); return INT; }

[\n]        { return EOLN; }

.           { printf("Illegal character %c: ", *yytext); }

我想要的是:

有输入:

a:=4+5
b:=a+2
b

输出应该是:

4+5+2

现在是正确的,但是如果我写输入(在同一个执行中)

a

输出为:4+5+2。

否则,如果(在另一个执行中)我的输入是:

a:=4+5
b:=2+a
b

先输入2+4+5,如果我写a,输出4+5,没有问题。

似乎,当一个操作的第一个表达式是一个字母时,它会将值放在主变量数组的正确位置(如果 b:=a+4,则主变量将是 b) , 如果它是一个字母,它会覆盖 $1 的值。

你能帮我解决一下吗?

您正在返回指向局部变量的指针,当这些局部变量超出范围时,这些指针会变成悬空。例如,在您的 ID 代码中:

{char string[2]; string[0]=;  string[1]=0;$$.id = ; $$.value=string;}

string 这里是一个本地栈上数组,它会随着这个块的退出而消失,所以 $$.value 变成悬空的,并且对它的任何使用都是未定义的。

然后,您也在使用 strcpy 用更长的字符串覆盖字符串,而不检查大小,这也是未定义的行为。

无论何时处理指针,都需要跟踪所指向事物的生命周期,并确保在生命周期结束后不使用指针。每当您使用指向数组(例如字符串)的指针时,您都需要跟踪底层存储数组的大小并确保不超过它。

您的代码充满了超过 运行 的缓冲区、未定义的行为和内存泄漏,其中 none 与您对 bison/yacc 的使用有很大关系。在这种情况下,任何输出都是可能的。

以下是一些错误(我在没有全面检查的情况下发现的错误):

char* concat(char* s1, char* s2, char* s3 ){
    char* final_string = malloc(sizeof(s1)+sizeof(s2)+sizeof(s3));

sizeof(s1) 是指向字符的指针的大小,可能是 4 或 8,具体取决于您是在 32 位还是 64 位环境中编译。它是在编译时计算的,因此它与 s1 指向的字符串的 运行 时间长度无关。那将是 strlen(s1).

但是把所有的sizeof都改成strlen是不够的。您还需要为即将插入到字符串中的 space 个字符以及末尾的 NUL 终止符分配足够的空间。

    char* ss1=malloc(sizeof(s1));
    char* ss2=malloc(sizeof(s2));
    char* ss3=malloc(sizeof(s3));
    strcpy(ss1, s1);    
    strcpy(ss2, s2);    
    strcpy(ss3, s3);

分配有与上述相同的问题:您使用 sizeof 而不是 strlen 并且您也没有为 NUL 终止符添加 1。此外,您永远不会 free() 存储临时字符串,因此它们最终都会泄漏。

与其修复所有这些,我建议您只删除这六行。无需制作临时副本。 strcat 不会覆盖其第二个参数指向的字符串,因此您可以在 strcat 调用中使用 s1s2s3

更好的方法是只使用 snprintf:

char* concat(const char* s1, const char* s2, const char* s3) {
    size_t len = strlen(s1) + srlen(s2) + strlen(s3) + 3;
    char* result = malloc(len);
    snprintf(result, len, "%s %s %s", s1, s2, s3);
    return result;
}

注意:如果你真的想学习如何编写计算机程序,你不会只是将其复制到你的项目中。您将尝试准确理解它的作用。您应该能够清楚地说明 len 的计算中 + 3 的原因, concat 的参数被声明为 const char* 而不是 [=33= 的原因] 以及 snprintf 如何安全地生成三个字符串的连接。

您还需要查看调用 concat:

的代码
  strcpy($$.value, concat(.value,"+",.value));

你没有初始化 $$.value 指向一个字符串缓冲区,所以你没有办法知道它指向的东西甚至存在,更不用说足够长的时间来复制 [=31= 的结果].但是无论如何您都没有理由复制该值;您知道 concat returns 是新分配的字符串。所以你可以直接使用它'

$$.value = concat(.value, "+", .value);

再次强调,理解 而不仅仅是复制对您来说很重要。赋值和调用 strcpy 有什么区别?

一旦你修复了所有这些问题,你仍然会泄漏内存,因为你从来没有 free() 分配的字符串。所以你应该考虑一下何时以及如何做。