我在令牌生成器中使用for pt in range(2, 100):
isp = 1
for ts in range(2, pt):
if pt % ts == 0:
isp = 0
break
if isp:
print(pt)
遇到了堆释放的问题。令牌生成器是递归下降解析计算器的一部分,该计算器可以完美地工作。但是在合并了对释放函数的调用后,它的行为就不稳定。实际上,计算器可能永远不会耗尽其堆,编写具有内存泄漏的程序只是一种不好的做法。
tokenize.h
free()
tokenize.c
#define OPERAND 0
#define OPERATOR 1
#define PARENTHESIS 2
#define TERMINAL 3
#define ADD '+'
#define SUBTRACT '-'
#define MULTIPLY '*'
#define DIVIDE '/'
#define EXPONENT '^'
#define L_PARENTHESIS '('
#define R_PARENTHESIS ')'
typedef struct {
int id;
char *value;
} token;
int token_count();
token *tokenize();
void deallocate();
答案 0 :(得分:7)
您的代码中存在多个问题:
ret
中未初始化的tokenize
。您应该改为返回NULL
。ret[ret_ind].value[size + 1] = '\0';
将空终止符在分配的数组中存储的距离太远了。应该是ret[ret_ind].value[size] = '\0';
malloc(size * sizeof(char) + 1)
是不一致的:如果您坚持使用sizeof(char)
(定义为1
的{{1}},则应该写malloc((size + 1) * sizeof(char))
,但是使用{{1 }},您还可以用简单的malloc(size + 1)
ret[ret_ind].value = strndup(string + i, k);
和L_PARENTHESIS
的情况可以合并为一个块。到达R_PARENTHESIS
令牌时,释放循环应停止。按照当前的编码,您不能处理不应生成的空列表,但是最好使实用程序功能对以后的更改更具弹性。
TERMINAL
token.h 中的原型应包括类型化的参数列表。
以下是简化版本:
void deallocate(token *in) {
if (in) {
for (int i = 0; in[i] != TERMINAL; i++)
free(in[i].value);
free(in);
}
}
以下是其余代码的补充说明:
为什么在进入和退出时清除屏幕?
您应该在主循环中测试文件结尾:
#include <stdio.h>
#include <stdlib.h>
#include "tokenize.h"
int token_count(const char *string) {
int count = 0;
int i = 0;
while (string[i] != '\0') {
switch (string[i++]) {
case ' ':
continue;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
i += strspn(string + i, "0123456789");
continue;
case ADD:
case SUBTRACT:
case MULTIPLY:
case DIVIDE:
case EXPONENT:
case L_PARENTHESIS:
case R_PARENTHESIS:
count++;
continue;
default:
return -1;
}
}
return count;
}
token *tokenize(const char *string) {
int count = token_count(string);
if (count <= 0)
return NULL;
token *ret = malloc((count + 1) * sizeof(token));
int i = 0;
int ret_ind = 0;
while (string[i] != '\0') {
if (string[i] >= '0' && string[i] <= '9') {
int size = strspn(string + i, "0123456789");
ret[ret_ind].id = OPERAND;
ret[ret_ind].value = strndup(string + i, size);
ret_ind++;
i += size;
continue;
}
switch (string[i]) {
case ' ':
i++;
continue;
case ADD:
case SUBTRACT:
case MULTIPLY:
case DIVIDE:
case EXPONENT:
ret[ret_ind].id = OPERATOR;
ret[ret_ind].value = malloc(2);
ret[ret_ind].value[0] = string[i];
ret[ret_ind].value[1] = '\0';
ret_ind++;
i++;
continue;
case L_PARENTHESIS:
case R_PARENTHESIS:
ret[ret_ind].id = PARENTHESIS;
ret[ret_ind].value = malloc(2);
ret[ret_ind].value[0] = string[i];
ret[ret_ind].value[1] = '\0';
ret_ind++;
i++;
continue;
default:
break;
}
break;
}
ret[ret_ind].id = TERMINAL;
return ret;
}
void deallocate(token *in) {
if (in) {
for (int i = 0; in[i] != TERMINAL; i++)
free(in[i].value);
free(in);
}
}
您应该有效地删除换行符:
if (!fgets(user_in, 1024, stdin))
break;
然后您可以简化退出测试:
#include <string.h>
user_in[strcspn(user_in, "\n")] = '\0';
无需在if (!strcmp(user_in, "exit"))
break;
之后清除user_in
您可以通过解决命令行参数来简化测试:
solve()
您应该忽略空格并接受空行
您应该使用for (int i = 1; i < argc; i++)
solve(argv[i]);
而不是"%.17g
。请注意,%lf
是强制性的
对于l
类型的scanf()
,但对于double
则被忽略,因为
传递给vararg的printf
参数将转换为float
像double
这样的功能。
您应该使用上下文结构并将指针传递给它
printf
及其辅助功能以避免全局变量
parse
和try_add_sub
中所见,
切换以统一令牌类型并避免使用try_mul_div
分类。
解析器太复杂:您应该更多使用递归下降
直接:OPERATOR
首先应调用try_add_sub
并进行迭代
加法运算符,为每个后续操作数调用try_mul_div
。
同样,try_mul_div
应该首先调用try_mul_div
,而try_exp
将
调用try_exp
来处理括号和常量。
此方法一次消耗一个令牌,可以从中读取 即时生成表达式源,而无需标记整个字符串。
您应该接受常量的整数语法,使用try_primitive
很容易。
这是沿这些方向的简化版本:
strtod()