我试图理解在以下场景中如何返回指针:
#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()
,当函数返回时,现在释放已分配的内存,因此可以覆盖该内存地址。
我想我的主要问题是,在整个程序中是否可以安全地传递指向字符串文字的指针?
答案 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 *
更准确地反映其性质。