试图理解指针返回类型的工作原理

时间:2011-12-30 23:33:24

标签: c++ pointers return-value return-type

我试图理解在以下场景中如何返回指针:

#include <iostream>
using namespace std;

// Why does this work? I can even pass the return value to another function
// and the contents do not change.
char* StringFromFunction()
{
  char* pReturn = "This string was created in the function.";
  return pReturn;
}

// I know this is wrong because the memory address where 5 is stored can be
// overwritten.
int* IntegerFromFunction()
{
  int returnValue = 5;
  return &returnValue;
}

int main()
{
  int*  pInteger;
  char* pString;

  pString = StringFromFunction();
  pInteger = IntegerFromFunction();

  cout << *pInteger << endl << pString << endl;

  return 0;
}

程序输出正如我所期望的那样:

5
This string was created in the function.

我在Visual C ++ 2010 Express中遇到的唯一编译器警告是“ c:\ vc2010projects \ test \ main.cpp(14):警告C4172:返回本地变量的地址或临时”并且它仅在我使用IntegerFromFunction()而非StringFromFunction()时显示。

我认为我从上面的例子中理解的是:

StringFromFunction()内,文本的内存分配“此字符串是在函数中创建的”。在执行时发生,因为它是一个字符串文字,即使在函数返回后内容仍然存在于内存中,这就是为什么pString中的指针main()可以传递给另一个函数而字符串可以在其中显示。

但是,对于IntegerFromFunction(),当函数返回时,现在释放已分配的内存,因此可以覆盖该内存地址。

我想我的主要问题是,在整个程序中是否可以安全地传递指向字符串文字的指针?

6 个答案:

答案 0 :(得分:4)

字符串文字实际上并不存储在自动变量等函数的堆栈中,而是存储在特殊位置(如全局变量)。

请注意,写入它们不可移植,因此最好将它们用作const char *而不是char *

答案 1 :(得分:3)

编译程序时,我的编译器g++会收到一条警告:

$ make strings
g++     strings.cc   -o strings
strings.cc: In function ‘char* StringFromFunction()’:
strings.cc:8:19: warning: deprecated conversion from string constant to ‘char*’
strings.cc: In function ‘int* IntegerFromFunction()’:
strings.cc:16:7: warning: address of local variable ‘returnValue’ returned

要避免警告,请在变量声明,函数返回类型和const函数中的变量前添加main()

GNU工具链将This string was created in the function.字符串存储在.rodata只读数据部分中,该部分在程序的生命周期内有效:

$ readelf -p .rodata strings

String dump of section '.rodata':
  [     8]  This string was created in the function.

当然,你不能修改字符串的内容,但这通常适用于静态编译到程序中的字符串。

答案 2 :(得分:3)

看到区别的最简单方法是生成一个简单的hello-world-ish示例的解集:

char* test() {
 return "Test";
}

int main(int argc, char* argv[]) {
 return 0;
}

这是在FreeBSD中使用gcc的diassembly,关闭了优化

    .file   "hellow.c"
    .section    .rodata
.LC0:
    .string "test"
    .text
    .p2align 4,,15
.globl test
    .type   test, @function
test:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $.LC0, %eax
    popl    %ebp
    ret
    .size   test, .-test
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    call    test
    movl    $0, %eax
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.2.1 20070719  [FreeBSD]"

如您所见,字符串文字本身存储在.LC0部分中,而不是存储在代码本身中。测试函数只返回指向.LC0(movl $ .LC0,%eax)开头的指针,因为这是第一个字符串文字。根据您编译的可执行格式,位置类似(但不相同)。阅读A.out(文本片段)或PE

答案 3 :(得分:2)

要理解的关键是要返回指针的对象的生存期(实际上,对象生存期是了解几乎所有对象实例的关键事项)。 C标准使用对象生存期的“存储持续时间”术语,因为在C中,对象实际上是表示值的数据存储区域。

字符串文字具有“静态存储持续时间”,表示(C99 6.2.4 / 3):

  

它的生命周期是整个程序的执行,它的存储值只在程序启动之前初始化一次。

因此,从函数返回指向字符串文字的指针是没有问题的(就指针引用的对象的生命周期而言)。字符串文字对象始终是有效对象。有一点需要注意的是,返回的指针将允许某人尝试修改包含字符串文字的数据数组,这是不允许的(这是未定义的行为)。

另一个示例中的本地int returnValue变量具有“自动存储持续时间”,这意味着(C99 6.2.4 / 4):

  

它的生命周期从入口延伸到与之关联的块,直到该块的执行以任何方式结束

(请注意,自动变长数组的生命周期略有不同)。

因此,当函数返回时,指向returnValue的指针变为无效。

我认为对象生存期是每个程序员都应该理解的基本内容之一,而且它在C和C ++中尤为重要,因为程序员主要负责正确处理它,特别是在处理指针时。

答案 4 :(得分:1)

是的,只要您只读取它们,就可以安全地传递指向字符串文字的指针。这是因为它们基本上是由编译器静态分配的,类似这样:

/* global/static variables go in the data section at compile time, not in the stack*/
char myString[6] = {'a', ' ',  'l', 'i', 't', '\0'};

char* StringFromFunction()
{
  char* pReturn = &myString[0]; //this pointer is not actually to inside this function!
  return pReturn; 
}

如果你想搞砸,试着在函数中分配一个实际的数组,而不是获得一个指向字符串文字的指针。

char* StringFromFunction()
{
    char myString[6] = {'a', ' ',  'l', 'i', 't', '\0'};
    return &myString[0];
}

请注意,字符串文字是常量且只读。你不应该试着写或更新它们,以免你陷入未定义的行为。

答案 5 :(得分:1)

"This string was created in the function."这样的字符串文字被放入只读内存中。您只能将它们分配给char *以获得向后兼容性,使用const char *更准确地反映其性质。