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

时间:2018-04-12 15:10:41

标签: c arrays yacc overwrite

我有下一个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( $2.code_action, $2.id, $2.value ); }
    | expr EOLN             { action( $1.code_action, $1.id, $1.value ); }
    ;


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



%%


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,则main将是b),并且如果它是一封信,它会覆盖1美元的价值。

你能帮我解决一下吗?

2 个答案:

答案 0 :(得分:3)

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

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

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

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

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

答案 1 :(得分:3)

您的代码充满了缓冲区溢出,未定义的行为和内存泄漏,这些都与您使用bison / yacc无关。在这种情况下,很好的输出是可能的。

以下是一些错误(没有经过全面检查我注意到的错误):

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

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

但仅将sizeof更改为strlen是不够的。您还需要为要插入字符串的空格字符以及最后的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不会覆盖其第二个参数指向的字符串,因此您可以在s1调用中使用s2s3strcat

更好的方法是使用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;
}

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

您还需要查看调用concat的代码:

  strcpy($$.value, concat($1.value,"+",$3.value));

你没有初始化$$.value指向一个字符串缓冲区,所以你无法知道它指向的内容甚至存在,更不用说复制concat的结果。但是你没有理由复制那个价值;你知道concat返回一个新分配的字符串。所以你可以使用它&#39;

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

同样,理解对您来说很重要,而不仅仅是复制。分配和对strcpy的调用之间有什么区别?

一旦你修复了所有这些,你仍然会泄漏内存,因为你永远不会free()分配的字符串。所以你应该考虑何时以及如何做到这一点。