【问题标题】:returning a "variable string literal" from a function从函数返回“可变字符串文字”
【发布时间】:2013-08-27 21:28:56
【问题描述】:

我有一些函数需要返回一个 const char*(这样一整套其他函数最终都可以使用它)。

我知道如果我有如下定义:

const char* Foo(int n)
{
    // Some code
    .
    .
    .
    return "string literal, say";
}

那么就没有问题了。但是我是否正确地说,如果 Foo 必须返回一些只能在运​​行时确定的字符串(取决于参数 n,(其中每个 n 取 [0, 2^31-1] 中的任何值)唯一地确定返回字符串)) 那么我必须使用堆(或返回像 std::string 这样在内部使用堆的对象)?

std::string 对于我想要完成的任务来说似乎太重了(至少有两个函数必须传递包裹),并且在 Foo 内分配内存以由调用者释放并不让我觉得安全向前走。我不能(轻松)传递对需要此函数的对象的引用,并不是说我相信无论如何都是可能的,但宏诡计是不可能的。

有什么简单的事情我还没有考虑到吗?

编辑

感谢大家的回答,我会选择 std::string (我想以迂回的方式要求确认没有办法向编译器暗示它应该存储一些 char 的内容[] 在它存储字符串文字的同一位置)。至于“重量级”(我很高兴复制它们并没有我想象的那么浪费)这不是最好的表达方式,也许“不同”会更接近我最初的担忧。

【问题讨论】:

  • 返回一个std::string 或者用2^31 case 编写一个switch语句...
  • std::string 有一个移动构造函数,所以你不必每次返回时都复制它。
  • std::string 对你的目的来说真的很重要吗?你怎么知道?您不会过早优化吗?
  • 您可以返回一个指向静态分配字符串的指针,但坦率地说,std::string 是显而易见的选择,它根本不是“重量级”。显然你不能真正使用字符串 literals 除非你想硬编码所有 20 亿个字符串。
  • 除了 std::string 的移动构造函数和移动赋值运算符之外,复制省略导致的副本比您天真的预期的要少。

标签: c++


【解决方案1】:

如果您的意思是您的函数在 n 编译时已知字符串之一之间进行选择,那么您可以只返回一个 const char * 给其中任何一个。字符串 literal 在 C 和 C++ 中具有静态存储持续时间,这意味着它们在程序的生命周期内都存在。因此返回一个指向 1 的指针是安全的。

const char* choose_string(int n)
{
  switch(n % 4)
  {
    case 0: return "zero";
    case 1: return "one";
    case 2: return "two";
    case 3: return "three";
  }
}

如果您的函数在运行时动态生成字符串,那么您必须传入 (char *buf, int buf_length) 并将结果写入其中,或者返回 std::string。

【讨论】:

  • 谢谢...切换是不可能的,每个 n 唯一地确定一个返回字符串。我同意返回 std::string 是前进的方向(我希望我在我的 OP 中更加明确,我希望确认任何搜索方法来告诉编译器将动态字符串存储在相同的静态存储位置正如它所做的那样, const char* 毫无意义,所以我应该停止寻找)
  • 你可以将字符串存储在static char result[1024] 中并返回一个指向它的指针,但这会激怒大多数程序员,因为它是不可重入的,不是线程安全的,而且通常很奇怪。
  • OP:“如果 Foo 必须返回一些只能在运​​行时确定的字符串”
  • @AndyThomas 好吧,这措辞模棱两可。它可以被解析为“一组常量字符串,只能在运行时确定要返回的字符串。”
  • 确实如此,但 OP 的“2^31-1”唯一确定的字符串强烈反对 20 亿大小写 switch 语句。
【解决方案2】:

在 C++ 中,返回 std::string 可能是正确的答案(正如其他几个人已经说过的那样)。

如果您出于某种原因不想使用std::string(例如,如果您使用 C 编程,但您会以这种方式标记问题),则有几个选项可以“返回”来自一个函数。没有一个是漂亮的。

  • 如果您返回一个字符串文字,那么您真正返回的是一个指向与该字符串文字关联的数组对象的第一个字符的指针。该对象具有静态存储持续时间(即,它存在于程序的整个执行过程中),因此返回指向它的指针是非常安全的。这显然是不灵活的。
  • 您可以在堆上分配一个数组并返回一个指向它的指针。这让被调用的函数可以确定它需要多长时间,但它会让调用者承担在不再需要内存时释放内存的负担。
  • 您可以返回指向函数内部定义的static 数组(的第一个元素)的指针。这是不灵活的,因为最大长度必须在编译时确定。这也意味着对该函数的连续调用将破坏结果。在 <time.h> <ctime> 中定义的 asctime() 函数执行此操作。 (我曾经写过一个循环遍历静态数组元素的函数,这样 6 次连续调用不会破坏以前的结果,但第 7 次会破坏。这可能是矫枉过正。)
  • 您可以要求调用者传递一个指向调用者本身必须分配的数组(的第一个元素)的指针,可能还需要一个单独的参数来指定调用者数组的长度。这要求调用者知道字符串可能有多长,并且可能能够处理没有保留足够空间的错误。

现在您知道为什么 C++ 提供像 std::string 这样的库功能来为您处理所有这些事情了。

顺便说一句,“可变字符串文字”这个短语没有多大意义。如果某物是文字,则它不是可变的。您的意思可能是“可变字符串”。

【讨论】:

  • 谢谢我同意你的观点,至于我对标题的措辞,是的,我认为我可以做得更好——更好! (我的意思是撇号暗示它们里面的东西应该在一个非常松散的意义上理解)。
【解决方案3】:

最简单的解决方案可能是返回std::string。

如果您想避免使用 std::string,另一种方法是让调用者将 char[] 缓冲区传递给函数。您可能还想提供一个函数来告诉调用者需要多大的缓冲区,除非上限是静态已知的。

【讨论】:

  • 在关于缓冲区大小的位之前抓住了我。是的,如果使用不当,这会带来缓冲区溢出的风险,以换取不使用 std::string 或在函数内分配内存——按照 OP 的要求。
【解决方案4】:

使用std::string,但如果你真的想要... C 编程中使用的一个常见模式是返回最终结果的大小,分配一个缓冲区,然后调用两次函数。 (我为 C 风格道歉,你想要一个 C 解决方案我给一个 C 解决方案:P)

size_t Foo(int n, char* buff, size_t buffSize)
{
  if (buff)
  {
    // check if buffSize is large enough if so fill
  }
  // calculate final string size and return
  return stringSize;
}

size_t size = Foo(x, NULL, 0); // find the size of the result
char* string = malloc(size); // allocate
Foo(x,string, size); // fill the buffer

【讨论】:

  • @PanagiotisKanavos 我不提倡这种方法,但它会完成工作。
  • @JustinMeiners - 你不应该在 C++ 中使用 malloc
  • @EdHeal 我同意使用new。 “我为 C 风格道歉,你要 C 方案我给 C 方案”
  • 错误代码不会“完成工作”。您所描述的是一个旧的 C 反模式。正确的 C 方法是由调用者分配数组并将其连同其大小一起传递,或者由函数分配数组并将其返回给调用者。
  • @PanagiotisKanavos 哦,好电话,为安全起见编辑不当。即使在这种情况下误用仍然会导致缓冲区溢出。在所有情况下,如果程序员传入了错误的缓冲区,他们都会得到错误的结果。
【解决方案5】:

(穿上石棉套装)

考虑只是泄漏内存。

const char* Foo(int n)
{
  static std::unordered_map<int, const char*> cache;
  if (!cache[n])
  {
    // Generate cache[n]
  }
  return cache[n];
}

是的,这会泄漏内存。最多 2^32 个字符串。但是如果你有实际的字符串文字,你会 always 在内存中有 all 2^32 个字符串(并且显然需要 64 位构建 - 仅 \0 就需要 4GB !)

【讨论】:

  • 所以它开始了......我不太确定在我的 OP 中我暗示这是我正在寻找的那种解决方案
  • “简单”&“尚未考虑”?这个解决方案的可行性主要取决于n 的分布,你还没有告诉我们。如果函数被调用了数百万次,但只有数百个n 的值,那么它就可以工作。 “泄漏”与看到的 n 值的数量成正比,而不是调用。
  • 我明白了,另一个我没有考虑过的简单解决方案是让函数向运行应用程序的人发送一封电子邮件,并询问他们应该返回什么!这个解决方案的可行性当然取决于我需要解决方案的速度和频率(当然,我从未指定细节!)。称我为偏执狂,但我不能接受任何信誉接近 62000 的海报都将其视为可行解决方案的主张。尤其是令人放心的评论,我会“总是在内存中拥有所有 2^32 个字符串”
  • 不仅可行,而且类似于我已经成功使用的东西。那些“内存中的 2^32 个字符串”适用于您将它们作为文字放入字符串表的情况。因为该表是在编译时构建的,并且必须包含所有可能的输入。此表仅包含实际输入。如果您仍然认为这个想法很古怪,请检查 Java 字符串实习的工作原理。
  • 我很抱歉,我完全误读了这个(在喜剧建议我可以硬编码 20 亿个字符串之后,我有点过于防御了)。仔细看一下,如果可以证明缓存大小超过某个界限是不可能的(如果有可能,我们还有其他更大的事情要担心),那么这很优雅的。问题是,在我短暂的职业生涯中,我见过太多“这永远不会发生”的案例,碰巧有信心将其提交代码审查!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-14
  • 2014-12-07
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多