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

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)

chq*_*lie 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定义,则应编写malloc((size + 1) * sizeof(char)),但是malloc(size + 1)在C 语言中使用是惯用的,还可以用一个简单的代码替换多行代码ret[ret_ind].value = strndup(string + i, k);
  • 案件的L_PARENTHESISR_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)