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
调用中使用 s1
、s2
和 s3
。
更好的方法是只使用 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()
分配的字符串。所以你应该考虑一下何时以及如何做。
我有下一个 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
调用中使用 s1
、s2
和 s3
。
更好的方法是只使用 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()
分配的字符串。所以你应该考虑一下何时以及如何做。