Kea*_*onB 0 c malloc memory-leaks dynamic
我free()在令牌生成器中使用堆释放时遇到问题。令牌生成器是递归下降解析计算器的一部分,该计算器可以完美地工作。但是在合并了对释放函数的调用后,它的行为就不稳定。实际上,计算器可能永远不会耗尽其堆,编写具有内存泄漏的程序只是一种不好的做法。
标记化
#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();
Run Code Online (Sandbox Code Playgroud)
标记化
#include <stdio.h>
#include <stdlib.h>
#include "tokenize.h"
int token_count(char string[]) {
int i = 0;
int count = 0;
while (string[i] != '\0') {
if (string[i] >= '0' && string[i] <= '9') {
while (1) {
i++;
if (string[i] >= '0' && string[i] <= '9') {
continue;
} else {
break;
}
}
count++;
continue;
}
switch (string[i]) {
case ADD:
case SUBTRACT:
case MULTIPLY:
case DIVIDE:
case EXPONENT:
case L_PARENTHESIS:
case R_PARENTHESIS:
count++;
i++;
continue;
default:
return 0;
break;
}
}
return count;
}
token *tokenize(char string[]) {
int i = 0;
token *ret;
int count = token_count(string);
if (!count) {
return ret;
}
ret = malloc((count + 1) * sizeof(token));
ret[count].id = TERMINAL;
int ret_ind = 0;
while (string[i] != '\0') {
if (string[i] >= '0' && string[i] <= '9') {
ret[ret_ind].id = OPERAND;
int size = 0;
int j = i;
while (1) {
size++;
j++;
if (string[j] >= '0' && string[j] <= '9') {
continue;
} else {
break;
}
}
ret[ret_ind].value = malloc(size * sizeof(char) + 1);
ret[ret_ind].value[size + 1] = '\0';
for(int k = 0; k < size; k++) {
ret[ret_ind].value[k] = string[i + k];
}
i = j;
ret_ind++;
continue;
}
switch (string[i]) {
case ADD:
case SUBTRACT:
case MULTIPLY:
case DIVIDE:
case EXPONENT:
ret[ret_ind].id = OPERATOR;
ret[ret_ind].value = malloc(2 * sizeof(char));
ret[ret_ind].value[0] = string[i];
ret[ret_ind].value[1] = '\0';
ret_ind++;
i++;
continue;
case L_PARENTHESIS:
ret[ret_ind].id = PARENTHESIS;
ret[ret_ind].value = malloc(2 * sizeof(char));
ret[ret_ind].value[0] = L_PARENTHESIS;
ret[ret_ind].value[1] = '\0';
ret_ind++;
i++;
continue;
case R_PARENTHESIS:
ret[ret_ind].id = PARENTHESIS;
ret[ret_ind].value = malloc(2 * sizeof(char));
ret[ret_ind].value[0] = R_PARENTHESIS;
ret[ret_ind].value[1] = '\0';
ret_ind++;
i++;
continue;
default:
break;
}
break;
}
return ret;
}
void deallocate(token *in) {
int i = 0;
while (1) {
free(in[i].value);
i++;
if (in[i].id == TERMINAL) {
break;
}
}
free(in);
return;
}
Run Code Online (Sandbox Code Playgroud)
您的代码中存在多个问题:
ret则从返回未初始化的tokenize。您应该返回NULL。ret[ret_ind].value[size + 1] = '\0';将空终止符在分配的数组中存储太远一步。它应该是ret[ret_ind].value[size] = '\0';malloc(size * sizeof(char) + 1)不一致:如果您坚持使用sizeof(char),这是1定义,则应编写malloc((size + 1) * sizeof(char)),但是malloc(size + 1)在C 语言中使用是惯用的,还可以用一个简单的代码替换多行代码ret[ret_ind].value = strndup(string + i, k);L_PARENTHESIS和R_PARENTHESIS可以合并成一个单一的块。到达TERMINAL令牌时,释放循环应停止。按照当前的编码,您不能处理不应产生的空列表,但是最好使实用程序功能对以后的更改更具弹性。
void deallocate(token *in) {
if (in) {
for (int i = 0; in[i] != TERMINAL; i++)
free(in[i].value);
free(in);
}
}
Run Code Online (Sandbox Code Playgroud)token.h中的原型应包括类型化的参数列表。
这是一个简化的版本:
void deallocate(token *in) {
if (in) {
for (int i = 0; in[i] != TERMINAL; i++)
free(in[i].value);
free(in);
}
}
Run Code Online (Sandbox Code Playgroud)
这是其余代码的其他说明:
为什么要在进入和退出时清除屏幕?
您应该在主循环中测试文件结尾:
if (!fgets(user_in, 1024, stdin))
break;
Run Code Online (Sandbox Code Playgroud)您应该有效地删除换行符:
#include <string.h>
user_in[strcspn(user_in, "\n")] = '\0';
Run Code Online (Sandbox Code Playgroud)那么您可以简化退出测试:
if (!strcmp(user_in, "exit"))
break;
Run Code Online (Sandbox Code Playgroud)user_in之后无需清除solve()
您可以通过解决命令行参数来简化测试:
for (int i = 1; i < argc; i++)
solve(argv[i]);
Run Code Online (Sandbox Code Playgroud)您应该忽略空格并接受空行
您应该使用"%.17g而不是%lf。请注意,l是强制性的scanf()一个double类型,但忽视了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()。
这是沿这些方向的简化版本:
#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);
}
}
Run Code Online (Sandbox Code Playgroud)