【问题标题】:Since I can't return a local variable, what's the best way to return a string from a C or C++ function?由于我无法返回局部变量,从 C 或 C++ 函数返回字符串的最佳方法是什么?
【发布时间】:2010-09-30 04:57:36
【问题描述】:

作为this question的后续行动:

据我所见,这应该可以按预期工作:

void greet(){
  char c[] = "Hello";
  greetWith(c);
  return;
}

但这会导致未定义的行为:

char *greet(){ 
  char c[] = "Hello";
  return c;
}

如果我是对的,修复第二个问候功能的最佳方法是什么?在嵌入式环境中?在桌面上?

【问题讨论】:

  • “我可以制造帆船、翼龙……” :-o 对不起,我有一架飞机!我读到问题的那一刻。
  • 其实行为是被定义的,数据是未定义的。下一个函数调用将清除 greet() 返回的数组。

标签: c++ c memory


【解决方案1】:

你完全正确。第二个示例中的 c 数组正在堆栈上分配,因此内存将立即被重用。特别是,如果你有类似的代码

 printf("%s\n",greet());

你会得到奇怪的结果,因为对 printf 的调用会重用你数组的一些空间。

解决方案是将内存分配到其他地方。例如:

char c[] = "Hello";

char * greet() {
    return c;
}

会工作的。另一种选择是在范围内静态分配它:

char * greet() {
    static char c[] = "Hello";
    return c;
}

因为静态内存是在数据空间中与堆栈分开分配的。

您的第三个选择是通过 malloc 在堆上分配它:

char * greet() {
   char * c = (char *) malloc(strlen("Hello")+1);  /* +1 for the null */
   strcpy(c, "Hello");
   return c;
}

但是现在你必须确保内存以某种方式被释放,否则你有内存泄漏。

更新

其中一个似乎比我预期更令人困惑的事情是“内存泄漏”到底是什么。泄漏是指您动态分配内存,但丢失了地址,因此无法释放。这些示例都不一定存在泄漏,但只有第三个示例甚至可能存在泄漏,因为它是唯一动态分配内存的示例。因此,假设第三种实现,您可以编写以下代码:

{
    /* stuff happens */
    printf("%s\n", greet());
}

这有泄漏;返回指向 malloc 内存的指针,printf 使用它,然后它就丢失了;你不能再释放它了。另一方面,

{
    char * cp ;
    /* stuff happens */
    cp = greet();
    printf("%s\n", cp);
    free(cp);
}

不会泄漏,因为指针保存在一个自动变量cp 中足够长的时间来调用free()。现在,即使 cp 在执行结束后立即消失,他结束大括号,由于已调用 free,内存被回收并且没有泄漏。

【讨论】:

  • 第三个例子是泄漏还是全部泄漏?
  • 它们都不一定泄漏;只有第三个可能泄漏。泄漏是指对动态分配的内存的引用丢失,因此无法释放;第三个选项会泄漏 IFF 返回的指针在内存被释放之前丢失。
  • 我只想补充一点,虽然第二个示例没有泄漏,但我相信它确实分配了在应用程序生命周期内保持分配的内存,这可能会导致类似泄漏的症状。
  • 废话。它分配静态内存一次。它与第一个示例完全相同的行为相同,只是名称仅在大括号内。
  • 这个答案,加上 Greg 和 j_random_hacker 的答案,列出了所有选项。不过,我希望您能列出每种方法的优缺点;例如,静态内存通常不在缓存中,因此不适合高性能代码,或者您必须记住释放 malloc 的字符串。
【解决方案2】:

如果您使用的是 C++,那么您可能需要考虑使用 std::string 从您的第二个函数返回字符串:

std::string greet() {
    char c[] = "Hello";
    return std::string(c); // note the use of the constructor call is redundant here
}

或者,在单线程环境中,您可以这样做:

char *greet() {
    static char c[] = "Hello";
    return c;
}

这里的static在全局内存区分配空间,永远不会消失。不过,这种static 方法充满了危险。

【讨论】:

  • 如果您也将其设为 const 并在全局范围内定义它,则危险性较小。
  • 我认为最好假设它是 C,因为它是嵌入式的。
  • Jesse:“c++”标签有点暗示它是一个选项。
  • @ChrisW:我在想 Greet() 做一些不平凡的事情并返回某种计算结果的情况。它必须将它存储在非常量的地方,并且您还必须处理 foo(greet(), greet()) 的情况。
【解决方案3】:

取决于嵌入式环境是否有堆,如果有,你应该如下malloc:

char* greet()
{
  char* ret = malloc(6 * sizeof(char)); // technically " * sizeof(char)" isn't needed since it is 1 by definition
  strcpy(ret,"hello");
  return ret;
}

请注意,您应该稍后调用 free() 进行清理。如果您无权访问动态分配,则需要将其设为全局变量或堆栈上的变量,但要在调用堆栈的更上方。

【讨论】:

  • 啊。比我的回答更好,也更少刻薄。
  • 我必须在 malloc 之后对 ret 进行空检查吗?
  • @nate:是的,你当然应该这样做。分配失败返回Null。
  • @jesse:你正在使用 malloc 的 calloc 语法... malloc 只接受一个参数,即要分配的块的大小(以字节为单位)。
  • 确实:calloc(6, sizeof(char)) 或 malloc(6 * sizeof(char))。在 C++ 中,如果没有堆,您可能会覆盖 operator new 以在预定义的内存中工作(可能保留在堆栈下方的空间或其他东西)
【解决方案4】:

如果您需要在 C++ 中执行此操作,按照 Greg Hewgill 的建议使用 std::string 绝对是最简单和可维护的策略。

如果您使用的是 C,您可能会考虑返回一个指向空间的指针,该指针已按照 Jesse Pepper 的建议使用 malloc() 动态分配;但另一种可以避免动态分配的方法是让greet() 采用char * 参数并将其输出写入那里:

void greet(char *buf, int size) {
    char c[] = "Hello";

    if (strlen(c) + 1 > size) {
        printf("Buffer size too small!");
        exit(1);
    }

    strcpy(buf, c);
}

size 参数是为了安全起见,以帮助防止缓冲区溢出。如果您确切知道字符串的长度,那就没有必要了。

【讨论】:

  • 不需要本地的char c[];通常使用这种方式时,数据会直接复制到buf中。
  • @CongXu:你是对的。在这个例子中,最好改写char *c = "Hello";,这样不会在堆栈上创建不必要的字符串文本副本。
【解决方案5】:

您可以使用以下任何一种:

char const* getIt() {
    return "hello";
}

char * getIt() {
    static char thing[] = "hello";
    return thing;
}

char * getIt() {
    char str[] = "hello";
    char * thing = new char[sizeof str];
    std::strcpy(thing, str);
    return thing;
}

shared_array<char> getIt() {
    char str[] = "hello";
    shared_array<char> thing(new char[sizeof str]);
    std::strcpy(thing.get(), str);
    return thing;
}

第一个要求您不要写入返回的字符串,但也是您可以获得的最简单的。最后一个使用 shared_array 如果对内存的引用丢失(最后一个 shared_array 超出范围),它可以自动清理内存。如果每次调用函数时都需要新字符串,则必须使用倒数和倒数。

【讨论】:

  • 我喜欢最后一种方法!
【解决方案6】:

按照其他几个响应的建议从函数返回 malloc 的内存只是要求内存泄漏。呼叫者必须知道您对其进行了 malloc 处理,然后才能免费呼叫。如果他们碰巧调用了 delete ,结果是不确定的(虽然可能没问题)。

最好强制调用者为您提供内存,然后将您的字符串复制到其中。这样来电者就会注意到他/她需要清理。

【讨论】:

    【解决方案7】:

    根据我的阅读,最安全的选择是让调用者负责分配内存来保存字符串。您还必须检查缓冲区是否足够大以容纳您的字符串:

    char *greet(char *buf, int size) {
         char *str = "Hello!"
         if (strlen(str) + 1 > size) { // Remember the terminal null!
              return NULL;
         } 
         strcpy(buf, str);
         return buf;
    }
    
    void do_greet() {
        char buf[SIZE];
        if (greet(buf, SIZE) == NULL) {
            printf("Stupid C");
         } 
         else {} // Greeted!
    }
    

    一个简单的任务需要做大量的工作...但是 C 适合你 :-) 哎呀!猜我被random_hacker打败了……

    【讨论】:

    • 我听说您不应该将字符串文字分配给 char*,而应将其分配给 char[],对吗?
    • 它们的含义不同。如果将文字分配给 char *,您将获得指向静态分配的只读文字的指针。如果你分配给一个 char[] 你会得到你自己的堆栈分配的本地副本来做你想做的事情。
    【解决方案8】:

    在堆中分配字符数组?

    您是否可以使用malloc 取决于您所说的“嵌入式环境”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-31
      • 2013-05-18
      • 2011-03-20
      • 2013-08-29
      • 1970-01-01
      • 2012-03-24
      • 1970-01-01
      相关资源
      最近更新 更多