我有下一个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美元的价值。
你能帮我解决一下吗?
答案 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
调用中使用s2
,s3
和strcat
。
更好的方法是使用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;
}
注意:如果您认真学习如何编程计算机,那么您不会将其复制到您的项目中。您将尝试准确理解它的作用。您应该能够清楚地说明计算+ 3
时len
的原因,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()
分配的字符串。所以你应该考虑何时以及如何做到这一点。