为什么我们可以从函数中 return char* ?
Why can we return char* from function?
这是一段 C++ 代码,显示了一些非常奇特的行为。谁能告诉我为什么strB可以打印出这些东西?
char* strA()
{
char str[] = "hello word";
return str;
}
char* strB()
{
char* str = "hello word";
return str;
}
int main()
{
cout<<strA()<<endl;
cout<<strB()<<endl;
}
String literals 在程序的生命周期内存在。
String literals have static storage duration, and thus exist in memory for the life of the program.
这意味着 cout<<strB()<<endl;
没问题,指向字符串文字 "hello word"
的 returned 指针仍然有效。
另一方面,cout<<strA()<<endl;
通向UB。 returned 指针指向本地数组的第一个元素 str
;当 strA()
returns 时被销毁,使 returned 指针悬空。
顺便说一句:字符串文字的类型为 const char[]
,char* str = "hello word";
再次从 C++11 开始无效。将其更改为const char* str = "hello word";
,并将strB()
的return类型也更改为const char*
。
String literals are not convertible or assignable to non-const CharT*
. An explicit cast (e.g. const_cast
) must be used if such conversion is wanted. (since C++11)
为什么 strB()
有效?
字符串文字(例如"a string literal"
)具有静态存储持续时间。这意味着它的生命周期跨越了程序执行的持续时间。这是可以做到的,因为编译器知道您将在程序中使用的 每个 字符串文字,因此它可以将它们的数据直接存储到已编译可执行文件的数据部分(示例:https://godbolt.org/z/7nErYe)
当您获得指向它的指针时,该指针可以自由传递(包括从函数 return 编辑)并取消引用,因为它指向的对象始终处于活动状态。
为什么 strA()
不起作用?
但是,从字符串文字初始化 char 数组会复制字符串文字的内容。创建的数组是与原始字符串文字不同的对象。如果这样的数组是一个局部变量(即具有自动存储持续时间),如在您的 strA()
中,那么它在函数 returns.
之后被销毁
当您从 strA()
return 时,由于 return 类型是 char*
执行“数组到指针转换”,创建指向数组的第一个元素。但是,由于数组在函数returns时被销毁,指针returned就失效了。您不应该尝试取消引用此类指针(并首先避免创建它们)。
案例 1:
#include <stdio.h>
char *strA() {
char str[] = "hello world";
return str;
}
int main(int argc, char **argv) {
puts(strA());
return 0;
}
语句 char str[] = "hello world";
在调用时(可能)被放入堆栈,并在函数退出后过期。如果你天真地认为这就是它在所有目标系统上的工作方式,你可以像这样编写可爱的代码,因为延续是在现有堆栈的顶部调用的(所以函数的数据仍然存在,因为它没有return尚未编辑):
你可以继续作弊:
#include <stdio.h>
void strA(void (*continuation)(char *)) {
char str[] = "hello world";
continuation(str);
}
void myContinuation(char *arg) {
puts(arg);
}
int main(int argc, char **argv) {
strA(myContinuation);
return 0;
}
案例2:
如果您使用下面的代码片段,文字“hello world”通常存储在受保护的只读内存中(尝试修改此字符串会在许多系统上导致分段错误,这类似于您的 main 和 strA 的存储方式,c 代码基本上只是一个 instructions/memory blob 的字符串,就像字符串是一串字符一样,但我离题了),即使您从未调用该函数,该字符串也将对程序可用知道它应该在特定系统上的地址。在下面的代码片段中,程序甚至没有调用函数就打印了字符串,这通常可以在相同的平台上工作,使用相对相同的代码和相同的编译器。虽然它被认为是未定义的行为。
#include <stdio.h>
char *strB() {
char *str = "hello world";
return str;
}
int main(int argc, char **argv) {
char *myStr;
// comment the line below and replace it with
// result of &myStr[0], in my case, result of &myStr[0] is 4231168
printf("is your string: %s.\n", (char *)4231168);
myStr = strB();
printf("str is at: %lld\n", &myStr[0]);
return 0;
}
您可以选择使用结构和相对安全的 strC。此结构在堆栈上创建并完全 returned。 strC 的 return 大小为 81(我为结构编造的任意数字,我相信自己会尊重)字节。
#include <stdio.h>
typedef struct {
char data[81];
} MY_STRING;
MY_STRING strC() {
MY_STRING str = {"what year is this?"};
return str;
}
int main(int argc, char **argv) {
puts(strC().data);
printf("size of strC's return: %d.\n", sizeof(strC()));
return 0;
}
tldr; strB 一旦从函数中 returns 就可能被 printf 损坏(因为 printf 现在有自己的堆栈),而 strA 中使用的字符串存在于函数外部,它基本上是一个指向全局常量的指针,可用作程序启动后(字符串在内存中与代码在内存中的方式没有区别)。
这是一段 C++ 代码,显示了一些非常奇特的行为。谁能告诉我为什么strB可以打印出这些东西?
char* strA()
{
char str[] = "hello word";
return str;
}
char* strB()
{
char* str = "hello word";
return str;
}
int main()
{
cout<<strA()<<endl;
cout<<strB()<<endl;
}
String literals 在程序的生命周期内存在。
String literals have static storage duration, and thus exist in memory for the life of the program.
这意味着 cout<<strB()<<endl;
没问题,指向字符串文字 "hello word"
的 returned 指针仍然有效。
另一方面,cout<<strA()<<endl;
通向UB。 returned 指针指向本地数组的第一个元素 str
;当 strA()
returns 时被销毁,使 returned 指针悬空。
顺便说一句:字符串文字的类型为 const char[]
,char* str = "hello word";
再次从 C++11 开始无效。将其更改为const char* str = "hello word";
,并将strB()
的return类型也更改为const char*
。
String literals are not convertible or assignable to non-const
CharT*
. An explicit cast (e.g.const_cast
) must be used if such conversion is wanted. (since C++11)
为什么 strB()
有效?
字符串文字(例如"a string literal"
)具有静态存储持续时间。这意味着它的生命周期跨越了程序执行的持续时间。这是可以做到的,因为编译器知道您将在程序中使用的 每个 字符串文字,因此它可以将它们的数据直接存储到已编译可执行文件的数据部分(示例:https://godbolt.org/z/7nErYe)
当您获得指向它的指针时,该指针可以自由传递(包括从函数 return 编辑)并取消引用,因为它指向的对象始终处于活动状态。
为什么 strA()
不起作用?
但是,从字符串文字初始化 char 数组会复制字符串文字的内容。创建的数组是与原始字符串文字不同的对象。如果这样的数组是一个局部变量(即具有自动存储持续时间),如在您的 strA()
中,那么它在函数 returns.
当您从 strA()
return 时,由于 return 类型是 char*
执行“数组到指针转换”,创建指向数组的第一个元素。但是,由于数组在函数returns时被销毁,指针returned就失效了。您不应该尝试取消引用此类指针(并首先避免创建它们)。
案例 1:
#include <stdio.h>
char *strA() {
char str[] = "hello world";
return str;
}
int main(int argc, char **argv) {
puts(strA());
return 0;
}
语句 char str[] = "hello world";
在调用时(可能)被放入堆栈,并在函数退出后过期。如果你天真地认为这就是它在所有目标系统上的工作方式,你可以像这样编写可爱的代码,因为延续是在现有堆栈的顶部调用的(所以函数的数据仍然存在,因为它没有return尚未编辑):
你可以继续作弊:
#include <stdio.h>
void strA(void (*continuation)(char *)) {
char str[] = "hello world";
continuation(str);
}
void myContinuation(char *arg) {
puts(arg);
}
int main(int argc, char **argv) {
strA(myContinuation);
return 0;
}
案例2: 如果您使用下面的代码片段,文字“hello world”通常存储在受保护的只读内存中(尝试修改此字符串会在许多系统上导致分段错误,这类似于您的 main 和 strA 的存储方式,c 代码基本上只是一个 instructions/memory blob 的字符串,就像字符串是一串字符一样,但我离题了),即使您从未调用该函数,该字符串也将对程序可用知道它应该在特定系统上的地址。在下面的代码片段中,程序甚至没有调用函数就打印了字符串,这通常可以在相同的平台上工作,使用相对相同的代码和相同的编译器。虽然它被认为是未定义的行为。
#include <stdio.h>
char *strB() {
char *str = "hello world";
return str;
}
int main(int argc, char **argv) {
char *myStr;
// comment the line below and replace it with
// result of &myStr[0], in my case, result of &myStr[0] is 4231168
printf("is your string: %s.\n", (char *)4231168);
myStr = strB();
printf("str is at: %lld\n", &myStr[0]);
return 0;
}
您可以选择使用结构和相对安全的 strC。此结构在堆栈上创建并完全 returned。 strC 的 return 大小为 81(我为结构编造的任意数字,我相信自己会尊重)字节。
#include <stdio.h>
typedef struct {
char data[81];
} MY_STRING;
MY_STRING strC() {
MY_STRING str = {"what year is this?"};
return str;
}
int main(int argc, char **argv) {
puts(strC().data);
printf("size of strC's return: %d.\n", sizeof(strC()));
return 0;
}
tldr; strB 一旦从函数中 returns 就可能被 printf 损坏(因为 printf 现在有自己的堆栈),而 strA 中使用的字符串存在于函数外部,它基本上是一个指向全局常量的指针,可用作程序启动后(字符串在内存中与代码在内存中的方式没有区别)。