【问题标题】:return substring of string返回字符串的子字符串
【发布时间】:2017-07-28 14:44:54
【问题描述】:

我有一个大字符串,我想在其中使用它的片段,但我不想复制它们,所以我想我可以制作一个结构来标记大字符串中有用块的开头和长度,然后创建一个读取它的函数。

struct descriptor {
  int start;
  int length;
};

到目前为止一切都很好,但是当我开始编写函数时,我意识到如果不复制到内存中,我就无法真正返回块......

char* getSegment(char* string, struct descriptor d) {
  char* chunk = malloc(d.length + 1);
  strncpy(chunk, string + d.start, d.length);
  chunk[d.length] = '\0';
  return chunk;
}

所以我的问题是:

  • 有什么方法可以在不复制的情况下返回该字符串
  • 如果没有,我该如何处理这种内存泄漏,因为副本在堆内存中,我无法控制谁将调用getSegment

【问题讨论】:

  • 能不能不直接回string + d.start?唯一的问题是如果有人要写入内存。其他问题是空终止符,因此您可能需要通过将 d 与新的子字符串一起传递来管理它
  • 不破坏原字符串,什么都不做。您必须将没有空终止的子字符串复制到其他空间以空终止它。您无法处理泄漏(使用该接口);您必须依靠调用者正确处理分配的内存。您可以定义char *getSegment(const char *source, struct descriptor d, char *output),其中调用者在output 参数中提供的空间必须至少为d.length+1 字节的内存。当然,调用者也要对那段内存负责。
  • @NeilLocketz “唯一的问题是......其他问题是”金:D
  • C 中没有可用的选项,调用者不必管理内存,除非您有垃圾收集,这并不常见并且完全改变了一切。在 C 中,您接受内存管理的责任。你可以设计一个更复杂的字符串结构,包括一个指针和管理这些字符串的函数,你可以有一个函数从其中一个字符串中提取一个子字符串,但是调用者仍然负责调用字符串释放函数。库中的代码无法判断调用代码何时完成了它提供的内容。
  • 代码不应该尝试string + d.start,因为string 不知道指向在该长度内有效的内存——即使string 很大,d.start 可能更大。首先找到strlen(string) 以避免这种情况。

标签: c string memory-management


【解决方案1】:

回答你的两个问题:

  1. 没有
  2. 调用者应该为复制的字符串提供缓冲区
  3. 我会亲自将指针传递给描述符

char* getSegment(const char* string, const char *buff, struct descriptor *d)

【讨论】:

  • 如果我像在我的示例中那样传递结构,是否将其复制到内存中?也许我对动态语言中的对象总是通过引用传递这一事实感到困惑,我认为结构与对象是等价的。
  • @php_nub_qq:是的,结构是复制的;不,当结构中只有 2 个整数时,成本不是主要问题。就个人而言,我可能会传递结构,而不是指向它的指针。如果结构变得更大(例如也包含指针),那么我会考虑将指针传递给结构。我也会考虑运行性能测量。'
【解决方案2】:

有什么方法可以不复制就返回字符串

一个字符串包括终止空字符,所以除非部分代码想要的是尾部,一个指向“一段字符串”的指针并且仍然是一个字符串,是不可能的。


我该如何处理这种内存泄漏,因为副本在堆内存中,我无法控制谁将调用 getSegment?

使用可变长度数组创建临时空间(从 C99 开始,在 C11 中支持可选)。很好,直到块结束。此时,内存被释放,不应进一步使用。

char* getSegment(char* string, struct descriptor d, char *dest) {
  // form result in `dest`
  return dest;
}

用法

   char *t;
   {
     struct descriptor des = bar();
     char *large_string = foo();
     char sub[des.length + 1u]; //VLA
     t = getSegment(large_string, des, sub);
     puts(t); // use sub or t;
  }
  // do not use `t` here, invalid pointer.

召回 size 是值得关注的。如果代码返回较大的子字符串,最好给malloc()一个缓冲区,并要求调用代码在完成后释放它。

【讨论】:

    【解决方案3】:
    • 有什么方法可以在不复制的情况下返回该字符串

    您是对的,如果您想将块与期望使用空终止字符数组的许多 C 函数中的任何一个一起使用,那么您必须进行复制。否则,添加终止符会修改原始字符串。

    但是,如果您准备将块处理为固定长度的 un 终止数组,那么您可以将它们表示为不复制为指向第一个字符的指针和长度的组合.一些标准库函数使用用户指定的字符串长度,因此支持对此类段的操作而无需空终止。但是,您需要对它们非常小心。

    如果你采用这种方法,我建议将指针和长度放在一个结构中。例如,

    struct string_segment {
        char *start;
        size_t length;
    };
    

    您可以声明这种类型的变量,传递和返回这种类型的对象,并在没有任何动态内存分配的情况下创建这种类型的复合文字,从而避免为内存泄漏开辟任何途径。

    • 如果不是,我该如何处理这种内存泄漏,因为副本位于堆内存中,我无法控制谁将调用 getSegment?

    返回动态分配的对象不会自动造成内存泄漏——它只是让调用者有责任释放分配的内存。当调用者未能履行该职责或将其传递给其他代码时,就会发生内存泄漏。一些标准库函数确实会返回动态分配的对象,这在第三方库中并不少见。典型示例(malloc() 本身除外)可能是 POSIX 标准的 strdup() 函数。

    如果你的函数返回一个指向动态分配对象的指针——无论是复制的字符串,还是块定义结构——那么它应该记录释放该对象的责任落在调用者身上。当您从自己的代码调用它时,您必须确保履行您的义务,但是在清楚地记录了该函数的行为后,您不能对其他调用者因未能履行其义务而可能犯的错误负责。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-27
      • 2015-09-18
      • 2021-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多