【问题标题】:How to free() my variables [closed]如何释放()我的变量[关闭]
【发布时间】:2016-05-05 05:35:02
【问题描述】:

如果我不知道freeme 的长度,我以后如何free() freeme 中的变量?我有一个把手,但我不知道长度。我可以添加一个计数器,然后在调用它的函数的末尾循环它,但通常有比最明显的方法更好的样式。我宁愿在函数中没有另一个变量。我已经对包含 booleans 持怀疑态度。

bool parse(bool b1, int i, int *j, int *e, char **ptr, int q, char **pString, char *str,
           char **freeme) {

    for (*e = 0; *(ptr + q + *e); (*e)++) {
        b1 = true;
        if (*(ptr + *e + q)) {
            str = concat(*pString, *(ptr + *e + q));
            *pString = concat(str, " ");
            free(str);
            freeme[*e] = *pString;
        }
        *j = *e;
    }
    return b1;
}

如果我理解正确,当我完成后,我必须在 freeme 数组中 free() all *pStringconcat 看起来像这样,我从这里的答案中得到它。 How do I concatenate two strings in C?

char *concat(char *s1, char *s2) {
    char *result = malloc(strlen(s1) + strlen(s2) + 1);//+1 for the zero-terminator
    //in real code you would check for errors in malloc here
    if (result == NULL) {
        fprintf(stderr, "malloc failed!\n");
        return (char *) '0';
    }
    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

【问题讨论】:

  • 通过让操作系统释放使用的内存?
  • @MikeCAT 但这是我的操作系统
  • 那么您应该提供更多背景信息。你的函数试图做什么?
  • 您是在问freeme 中需要释放多少元素?假设freeme 是动态分配的,那么C 不提供任何自动方法让您知道该内存块有多大(即,如果用作数组,它有多少元素)。您需要自己跟踪分配的大小。
  • coughs 他可以使用 Rust 语言 coughs C 不是唯一的系统编程语言。

标签: c pointers


【解决方案1】:

学习 C 的一个重要方面是学会在抽象方面做出正确的选择。词法分析器/解析器本身就足够复杂,不应该乱扔原始的低级指针和字符串操作,这容易出错并降低可读性。

因此,与其编写解析代码,不如先为自己编写一些健壮且可测试的抽象(就 C 中的抽象而言...)。

您的解析函数的freeme 参数,例如对我来说看起来像一个StringList。因此,最好先为自己编写一个 StringList 模块,并使用列表的常规操作。

此外,特别是在解析器的上下文中,字符串和其他动态分配的对象通常保存在多个数据结构中。因此,问题出现了,哪个容器“拥有”该实例。 IE。谁能释放它?这将是另一个基本方面,如果处理得当,可以帮助以更简洁和不易出错的方式编写解析器本身。

有了所有这些,您的问题本身就已经过时了,因为StringList 模块将解决这些细节问题,而不是您的解析器代码。

最后同样重要的是,您将能够为此类事物选择现成的抽象,因为它们之前已经被编写和使用过数千次。 FreeBSD 内核代码是纯 C 语言,代码非常清晰且经过良好测试,我相信您会找到合适的。

应用此建议可能会使您的代码看起来更像这样:

bool parse(InputIterator * source_iter, StringList * tokens ) 
{
     char currentToken[MAX_TOKEN_LENGTH];
     /* ... */
     for( ; !InputIterator_end(source_iter); InputIterator_advance(source_iter, 1)) {
         char current = InputIterator_current(source_iter);
         if(isws(current)) {
            StringList_append( tokens, currentToken);
            skip_whitespaces(source_iter); /* another helper function of yours*/
         }
         else {
             string_append_char( currentToken, sizeof(currentToken), current); // no need to write this logic 100 times all over your parser.
         }
     }
     return true; 
}

根据我的经验,这种 C 编程风格运行良好,生成可测试的小函数,并且与“一体式”方法相比更具可读性。

【讨论】:

    【解决方案2】:

    如果我正确阅读了您的 parse 函数,它似乎正在遍历 ptr 指定的字符串列表。列表本身以 null 结尾,列表中的起始偏移量位于索引 q

    解析函数的目标似乎是生成一个输出字符串,它是所有输入字符串的串联,但在每个字符串之间插入了一个分隔符字符串str 和一个space。类似于以下内容:

     result = ""
     for each string s in ptr:
         result += s
         result += delimiter
         result += " "
     return result;
    

    令人困惑的是parse 函数的一些输入参数实际上只是局部变量或根本没有使用。这包括e',i, andb1`。

    因此,使您的问题变得困难的是将每个字符串连接在一起并尝试跟踪稍后释放每个临时分配的内存管理。你真正需要的是一个字符串类。但这是 C,而不是 C++。幸运的是,我们可以通过结构类型和一些有价值的辅助函数来克服这个问题。

    我建议让我们从一个为连接优化的有价值的字符串类开始。让我们定义一个“SimpleString”如下:

    typedef struct _SimpleString
    {
        char* str;          // the actual null terminated string
        size_t length;      // length of psz, not including null char
        size_t allocated;   // amount of memory malloc'd including room for null char
    } SimpleString;
    

    这里结构的str 是原始字符串指针。

    现在让我们创建一个简单的“构造函数”来创建一个字符串:

    SimpleString* create_string()
    {
        SimpleString* s = (SimpleString*)malloc(sizeof(SimpleString));
        if (s == NULL)
        {
            return NULL; // out of memory
        }
    
        s->str = malloc(1);
    
        if (s->str == NULL)
        {
            free(s);
            return NULL;
        }
    
        s->str[0] = '\0';
        s->length = 0;
        s->allocated = 1;
    
        return s;
    }
    

    上述函数将“malloc”一个 SimpleString 实例并返回指向它的指针。 SimpleString 的内部成员str 本身被初始化为一个空字符串。

    现在,当我们想连接到这个字符串时,我们可以使用我们的辅助函数,如下所示:

    int concat_string(SimpleString* s, const char* p)
    {
        size_t needed = 0;
        size_t p_len = p ? strlen(p) : 0;
    
        if (p == NULL)
        {
            return 0;
        }
    
        if (p_len == 0)
        {
            // nothing to do
            return 1;
        }
    
        if (s->str)
        {
            needed += s->length;
        }
    
        needed += p_len;
        needed += 1; // for null char
    
        if (needed > s->allocated)
        {
            size_t newallocation = needed * 2; // allocate more than needed so that we don't have to reallocate and re-copy the buffer on each call to concat_string
            char* newstring = malloc(newallocation);
            if (newstring == NULL)
            {
                // out of memory
                return 0;
            }
    
            newstring[0] = '\0';
    
            s->allocated = newallocation;
    
            if (s->str && (s->length > 0))
            {
                memcpy(newstring, s->str, s->length);
            }
    
            free(s->str);
            s->str = newstring;
        }
    
        memcpy(s->str + s->length, p, p_len);
        s->str[s->length + p_len] = '\0';
        s->length += p_len;
        return 1;
    }
    

    随着字符串的增长,上述函数将负责重新分配额外的内存(并释放旧内存)。

    现在终于有了一个释放字符串的辅助函数:

    void release_string(SimpleString* s)
    {
        if (s)
        {
            free(s->str);
            free(s);
        }
    }
    

    现在我们可以大大简化你的解析函数如下:

    SimpleString* parse(char **list, int q, const char *delimiter)
    {
        SimpleString* result = NULL;
        char *current = list[q];
        int count = 0;
    
        while (current != NULL)
        {
            if (result == NULL)
            {
                result = create_string();
                if (result == NULL)
                {
                    // error! out of memory
                    break;
                }
            }
    
            concat_string(result, current);
            concat_string(result, delimiter);
            concat_string(result, " ");
    
            count++;
            current = list[q + count];
        }
    
        return result;
    }
    

    在上述函数中,我将您的 ptr 参数重命名为 list 并将您的 str 参数重命名为 delimiter。我单独留下了q 作为列表中的初始偏移量。

    我怀疑您真的只想在除最后一次迭代之外的所有内容上连接尾随空格。如果是这样,我会把它留给你做练习。

    在您的原始实现中,您使用参数j 来指示使用并作为输出参数返回的字符串计数。您可以轻松地将其添加回去。 (例如*j = count;

    示例用法:

    int main()
    {
        char* list[] = { "Mecury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", NULL };
    
        SimpleString* result = parse(list, 0, ",");
    
        printf("%s\n", result->str);
    
        release_string(result);
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      你可以引入另一个参数来统计freeme中的元素。

      bool parse(bool b1, int i, int *j, int *e, char **ptr, int q, char **pString, char *str,
                 char **freeme, int *freeme_len) {
      
          for (*e = 0; *(ptr + q + *e); (*e)++) {
              b1 = true;
              if (*(ptr + *e + q)) {
                  str = concat(*pString, *(ptr + *e + q));
                  *pString = concat(str, " ");
                  free(str);
                  //freeme[*e] = *pString;
                  freeme[(*freeme_len)++] = *pString;
              }
              *j = *e;
          }
          return b1;
      }
      

      在调用 parse() 之后,您迭代所有 freeme 元素并释放它们。

      void main() {
        //The following line outlines that freeme is allocated somewhere and it's type is char**
        //and should not be taken literally. It's size should be calculated, according 
        //to the situation, or should be dynamically resized during operations.
        char **freeme = malloc(1000 * sizeof(char*));
      
        //freeme_len should be initialized to 0 before first calling to parse().
        int freeme_len = 0;
      
        //here follows some program code, that we don't know about, which declares and 
        //initializes all remaining variables
        parse(b1, i, &j, &e, ptr, q, &pString, str, freeme, &freeme_len);
      
        //After calling, free all
        for(i=0; i<freeme_len; i++) {
          free( freeme[i] );
        }
      
        free(freeme);
      }
      

      【讨论】:

      • 简单直接!它只做了很小的改动。顺便说一句,您不能更改我的e,我需要它来进行标记化。因此,我从您更改的那一行中制作了 2 行,然后只增加一行并保留我的旧行。现在我在 Valgrind 中有 0 个错误和 0 个内存泄漏。
      • 这仍然是非常危险的代码 - freeme 中有多少 容量 可用?看起来它被用作没有边界检查的列表。 (实际上,整个界面看起来都是错误的,但由于问题没有main() 或其他上下文,我们只能猜测......)
      • main() 中的代码是象征性的,它的目的是展示如何使用变量freeme_len 来取消分配剩余的字符串。我们不知道freeme 有什么容量。我写了这个 malloc(1000*4) 只是为了概述它必须分配到某个地方。我将编辑代码以添加有关它的 cmets。我不争论是对是错。我们不知道更大的图景。这只是针对具体问题的简单具体解决方案。
      猜你喜欢
      • 2014-12-17
      • 1970-01-01
      • 2017-05-02
      • 2015-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      相关资源
      最近更新 更多