如何使用free释放使用malloc进行的堆分配?

时间:2019-06-29 14:27:11

标签: c dynamic memory-leaks malloc

我在令牌生成器中使用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();

1 个答案:

答案 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参数将转换为floatdouble这样的功能。

  • 您应该使用上下文结构并将指针传递给它 printf及其辅助功能以避免全局变量

  • 如您在parsetry_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()