【问题标题】:Returning strings in C. Why is this working?在 C 中返回字符串。为什么会这样?
【发布时间】:2012-10-01 22:20:22
【问题描述】:

对不起,如果这里已经发布过类似的东西,但我真的找不到。

我有以下代码,尽管我会说这是不正确的,但我得到了正确的答案。

char *selectStr(int index){
    char *str[] = {
        "hello",
        "hola",
        "epa",
        "alright",
    };
    return str[index];
}

int main() {
    printf("String: %s\n", selectStr(2));
    return 0;
}

谁能告诉我为什么这真的有效?我看到它的方式:字符串数组str 是 selectStr 函数内的局部变量。此函数返回包含在此数组中的字符串。但是由于这个字符串数组str 是一个局部变量,它应该在它返回后从内存中清除(对吗?),所以我预计会出现某种内存访问错误。

我应该认为自己很幸运,这段代码有效(即这是一种未定义的行为吗?)还是这实际上是一种很好的做事方式(在这种情况下,为什么)?

我的猜测是,指向数组str 的指针在函数返回后被清除,但它指向的实际内容不会被清除,它会一直保留在内存中,直到有其他内容写入它为止。如果有人能证实这一点,或者告诉我实际发生了什么,我将不胜感激。

提前致谢!

PS:我会这样做的方法是将缓冲区作为参数传递,但我只是想知道为什么,令人惊讶的是——至少对我来说——这确实有效。

【问题讨论】:

  • 数组是本地的。但文字不是。您正在返回文字。

标签: c string memory parameter-passing


【解决方案1】:

谁能告诉我为什么这真的有效?

您假设这是未定义的行为,而 UB 并不意味着“您的代码会以明显的方式崩溃或失败”,这意味着任何事情都可能发生。

在这种情况下,您可以设想用于存储本地的堆栈空间可能尚未被重用,并且可能仍包含原始值。它可能会在某些时候失败,但你不能期望它会以明显的方式失败。

但是,您没有在此处调用 UB,因为您正在返回指向静态分配的字符串文字的指针(并且可能存储在只读内存中,因此您确实应该返回 const char*)。 array 是本地的; 字符串不是。

PS:我会这样做的方式是将缓冲区作为参数传递...

您还可以在函数中使数组成为静态的。这是否是一个好主意取决于您的整体设计。

【讨论】:

  • 创建数组static(或者更好的static const)将摆脱未定义的行为。 const 会更好,因为它可以防止有人弄乱你的函数返回的字符串。
  • @Will:我在底部提到了一个静态数组,但是在这种情况下,没有UB。字符串不是函数的本地字符串,这是一种特殊情况。
  • 不,我想他们不是。不过,如果您将常量定义为函数的一部分,最好使用 const。否则返回带有strdup() 的副本可能是个好主意。你永远不知道会唤醒什么样的虫子。
  • @will:哦,是的,我不反对。返回类型显然应该是const char* 或者返回一个副本,数组也应该是静态的。
【解决方案2】:

字符串常量,例如“anything”,不存储在堆栈中。您的堆栈(在 selectStr() 中)包含一个指针数组,并且您正在返回一个指针,而不是堆栈上指针的地址。在这种情况下,您的指针将始终有效。

【讨论】:

  • C 中没有堆栈或只读内存的概念。所以,这个答案是无效的。
  • @RichardJ.RossIII:我同意“自动存储持续时间”是一个更好的说法,但是来吧;实际上,我曾经使用过的每个 C 编译器都使用堆栈作为本地变量。
  • 函数肯定有一个栈,栈上是一个指针数组。这些指针指向只读内存,所以本质上你同意我的回答,但方式很奇怪。
  • @mahH 不,堆栈是一种实现细节,通常用于实现具有自动存储持续时间的变量的存储机制。 C 规范不强制要求使用堆栈(或任何其他)结构,只是该语言定义的语义保持真实。理查德是正确的,虽然有点迂腐。
  • @Ed - 如果我创建一个只有 selectStr() 函数的文件并将其传递给gcc -S,反汇编显示一个 16 字节的堆栈;每个指针 4 个字节。
【解决方案3】:

这段代码是正确的。

char *str[] = {
    "hello",
    "hola",
    "epa",
    "alright",
};

不会创建一个字符串数组。

它创建一个指向字符串的指针数组。字符串本身具有静态存储持续时间。指针数组具有自动存储持续时间。当函数返回时,指针数组变为禁区,但字符串仍然存在。

return str[index];

这会取消对数组的引用以检索指向其中一个字符串的指针,并返回该指针。 str 超出范围,但指针仍指向静态存储中的字符串。

str 标记为static 并没有什么坏处。优化器可能已经这样做了。所以你可以写:

static char *str[] = {
    "hello",
    "hola",
    "epa",
    "alright",
};

如果这样会让你更舒服。

【讨论】:

  • 我也会让那些char * 成为const
  • “它创建了一个指向字符串的指针数组。” - 嗯... C 中的字符串是一个字符数组或指向一系列字符的指针一个 NULL 字符。所以实际上,它是一个字符串数组,以及一个指向char 的指针数组。指向字符串的指针将是 char** 或类似的东西。只是挑剔。
  • @EdS.:不,术语字符串被定义为仅以空字符结尾的字符序列。指向该数组的指针始终是“指向字符串的指针”。请参阅标准中的 7.1.1.1。
  • @EdS.:你说得对,但这是一个与解释无关的细节,我不想从相关细节中混淆指针与字符存储不同。
  • 好吧,再想一想,我也可以接受你的解释。它是一个指向 char 的指针数组。如果我认为字符串是字符的集合,那么它确实是一个指向字符串的指针数组。
【解决方案4】:

我是否应该认为自己很幸运,这段代码有效(即,这是一个 未定义的行为?)或者这实际上是一种很好的做事方式 (在这种情况下,为什么)?

可能 =) 虽然,从你提出的论点来看,这很难说。出于所有意图和目的,它可能出于技术原因工作,但不是一个好主意。

我会在函数内部将数组设为静态,但如果这仍然让这里的人们感到不安,为什么不增加范围呢?

static const char *str[] = {
    "hello",
    "hola",
    "epa",
    "alright"
};

const char *selectStr(int index){
    return str[index];
}

当您将此函数与main 放在同一个文件中时,关于全局变量的程序设计考虑并没有真正考虑到...源文件并导出selectStr 函数。不用担心。

我要做的唯一调整是将数组命名为比 str 更明显的全局名称。

【讨论】:

  • 我个人希望范围尽可能窄。虽然不是UB。将数组设为静态会更好,因为它清楚地传达了意图,但两种方法都有效。
  • ...“在字符串文字的情况下”我应该添加。
猜你喜欢
  • 1970-01-01
  • 2016-06-27
  • 2021-05-12
  • 1970-01-01
  • 1970-01-01
  • 2012-07-29
  • 1970-01-01
  • 2014-08-23
  • 1970-01-01
相关资源
最近更新 更多