【问题标题】:My program is good enough for my assignment but I know its not good我的程序对我的任务来说足够好,但我知道它不好
【发布时间】:2010-03-26 17:01:07
【问题描述】:

我刚刚开始为 uni 分配任务,这对我提出了一个问题。

我不明白如何在没有内存泄漏的情况下从函数返回字符串。

char* trim(char* line)  {
    int start = 0;
 int end = strlen(line) - 1;

 /* find the start position of the string */
 while(isspace(line[start]) != 0)  {
     start++;
 }
 //printf("start is %d\n", start);

 /* find the position end of the string */
 while(isspace(line[end]) != 0)  {
     end--;
 }
 //printf("end is %d\n", end);

 /* calculate string length and add 1 for the sentinel */
 int len = end - start + 2;

 /* initialise char array to len and read in characters */
 int i;
 char* trimmed = calloc(sizeof(char), len);

 for(i = 0; i < (len - 1); i++)  {
     trimmed[i] = line[start + i];
 }
 trimmed[len - 1] = '\0';

    return trimmed;
}

如您所见,我正在返回一个指向 char 的指针,它是一个数组。我发现如果我尝试通过以下方式制作“修剪”数组:

char trimmed[len];

然后编译器会抛出一条消息,指出在这一行上需要一个常量。我认为这意味着由于某种原因,您在初始化数组时不能使用变量作为数组长度,尽管有些事情告诉我这是不对的。

因此,我通过将一些内存分配给 char 指针来创建数组。

我知道这个函数对于它试图做的事情可能是次优的,但我真正想知道的是:

  1. 你可以正常初始化一个数组使用一个变量来声明长度吗:

    字符修剪[len];

  2. 如果我有一个该类型的数组(char trimmed[]),它的返回类型是否与指向 char 的指针(即 char*)相同。

  3. 如果我通过分配一些内存并将其分配给一个 char 指针来创建我的数组,我该如何释放这些内存。在我看来,一旦我返回了这个数组,我就无法访问它来释放它,因为它是一个局部变量。

【问题讨论】:

  • “我认为这意味着由于某种原因你不能在初始化数组时使用变量作为数组长度,尽管有些事情告诉我这是不对的。”不,这是正确的,数组大小必须是编译时常量。
  • +1 用于跟进某个主题,即使您知道它“足以胜任 [您的] 任务”
  • 这并没有具体回答您的问题,但这种情况下的另一种选择是就地修剪字符串。这是可能的,因为修剪操作永远不会增加字符串的长度。显然,需要修改使用您的方法的代码以处理此更改。
  • 正如 Neil 在他的回答中指出的那样,detly 的评论对于现代 C 来说是错误的。C99 标准允许在数组声明中使用变量。所以它是有效的代码。但是,许多编译器默认为 C89 模式,以防万一您希望您的代码在 DEC VAX 或其他设备上编译。这可能就是您收到错误的原因。
  • 我认为杰弗里的回答是最优雅的。这将导致为字符串对象持有的字符串分配所有原始内存,但在正常使用中似乎不太可能有很多空白,即没有很多浪费。

标签: c


【解决方案1】:

到地址 (3) - 完成后,您可以从调用代码中 free 新分配的字符串:

char* tmp = trim(myline);

if (tmp != NULL) {
    ....
    free( tmp );
}

但这给调用者带来了记住释放内存的负担。因此,您可以考虑将分配的缓冲区和缓冲区大小传递给trim(),例如:

void trim(char* line, char *trimmed_buf, int trimmed_buf_len){ ... }

尼尔在解决您的其他问题方面做得非常出色。基本上,数组声明 char trimmed[len]; 将在 stack 上声明一个局部变量,因此虽然将 char * 返回到此内存在语法上是正确的,但它指向的内存位置将不再是有效。

【讨论】:

  • 这是最好的答案,因为它允许调用者使用分配在堆栈或他们选择的任何堆上的数组,而不是强制使用特定的内存管理模型。主要是传入起始指针和长度,这样就不会被覆盖。
  • 虽然很常见,但在调用free 之前检查NULL 是没有意义的。 free 函数已经在内部进行了 NULL 测试,并且再次添加测试不会增加任何价值(相反,我认为它通过噪声提供负值)。
  • +1,感谢您的提示!我只是把它包括在内,以防他在调用 free 之前尝试对指针做一些事情,在这种情况下,NULL 检查是个好主意。
  • 感谢贾斯汀和所有发帖的人,这对我们有很大帮助。我现在看到我仍然可以访问函数之外的调用内存。那是我没有得到的。我以为我和它永远失去了联系。但是返回的是一个指向该内存地址的指针,所以我可以使用它来跟踪它并释放它。酷。
  • 需要在内存分配后进行NULL检查,但我强烈建议将测试反转为if (tmp == NULL) {...},以便不测试正常情况,但测试异常(参见stackoverflow.com/questions/114342/…
【解决方案2】:

要回答您的具体问题,语法:

char trimmed[len];

其中len 是一个变量,仅在C99 中允许,在C89 或C++ 中不允许。返回类型确实是char *,但是返回局部变量trimmed 会导致未定义的行为,所以不要这样做。如果你通过calloc在函数中动态分配一个数组并返回它,则由函数的调用者使用函数返回的指针来释放它。

【讨论】:

    【解决方案3】:

    关于像char trimmed[len]; 之类的数组声明的动态调整大小,最新版本的C 标准(ISO/IEC 9899:1999) 允许这样做,但对于这个函数,它根本没有帮助。 trimmed 变量的作用域在 trim 函数内,并在堆栈上分配。因此,如果您将return trimmed; 放入您的代码中,您将返回一个指向堆栈上变量的指针,并且该变量所在的堆栈部分将在函数返回的点被释放,因此效果不会那么好...

    【讨论】:

      【解决方案4】:

      让调用者将一个指向内存区域(最大长度)的指针传递给函数。这样,调用者将负责分配(和解除分配)内存,并且函数将只提供限制在传递给函数的缓冲区的输出。

      当然,使用该函数的人仍然可能把事情搞砸(通过指定一个与实际缓冲区大小没有任何关系的长度,但是您可以正确地辩称这是调用者的错,而不是这个函数的错。

      【讨论】:

        【解决方案5】:

        除了使用std::string 之类的 C++ 解决方案之外,您始终可以分配一个设定大小的数组,并将该大小作为参数传入,然后将该数组作为引用或指针的参数传递?

        这样数据在同一个作用域内分配,没有内存泄漏。

        然后你总是可以在调用后释放内存。这意味着负责创建它的代码和负责销毁数据的代码是不一样的,这可能是错误和错误的前兆。

        【讨论】:

          【解决方案6】:

          函数外的分配和删除:

          使用 char trimmed[SIZE] 从堆栈中分配它并将其传递给函数,或者使用 calloc 从堆中分配它并将其传递给函数。

          函数内部分配,外部删除:

          你使用 calloc 在函数内部分配它,但调用者必须使用 free 释放它。

          【讨论】:

            【解决方案7】:

            嗯,好吧,回答你的问题:

            1. 是的,数组将分配在堆栈上,而不是堆上,并在函数返回时被释放。
            2. 是的,char[] 基本上等同于 char*。最好将它们分开,因为存在语义差异。
            3. 使用任何指向内存的指针,都可以使用free。在您的情况下,您将释放返回函数。这被认为是非常糟糕的形式并且容易出错。您通常希望分配器成为 free()er。也许,您可以为新空间传递一个缓冲区,或者函数的调用者可以同意将char * line 内容写入现有内容之上。这适用于 trim() 总是只会删除东西。我认为传入的缓冲区会更频繁地工作,并且养成习惯是一件好事。

            至于您的函数,请考虑使用 memcpy() 或其表兄弟将字节复制到 char 缓冲区中或从中复制出来。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-11-11
              • 2016-10-12
              • 1970-01-01
              • 2013-07-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-13
              相关资源
              最近更新 更多