C 中的指针作用域
Pointer scopes in C
在下面的代码中,无法打印任何内容的解释是get_message()返回的指针超出范围:
char *get_message() {
char msg [] = "Aren’t pointers fun?";
return msg ;
}
int main (void) {
char *foo = get_message();
puts(foo);
return 0;
}
在gdb中运行时,发现foo位置的数据是字符串"Aren't pointers fun?":
Old value = 0x0
New value = 0x7fffffffde60 "Aren’t pointers fun?"
(这似乎与指出超出范围的指针的数据保留在内存中的答案一致),但 "puts" 的文档指出第一个数据是从给定的地址复制的:大概是 0x7fffffffde60在这种情况下。
因此:为什么没有输出?
编辑:感谢您的回答:
我 运行 原始代码在 gdb 中完成,对 puts 的调用确实更改了存储 foo 的地址处的数据。
(gdb) p foo
= 0x7fffffffde60 "Aren’t pointers fun?"
(gdb) n
11 return 0;
(gdb) p foo
= 0x7fffffffde60 "`67777"
有趣的是,当我将 change_msg() 的代码更改为:
时,代码 did 打印了消息
char *get_message() {
char *msg = "Aren’t pointers fun?";
return msg ;
}
在这种情况下,foo 处的数据(地址 0x4005f4 - 较小的地址有什么意义吗?)在整个代码中保持不变。找出为什么这会改变行为会很酷
变量msg分配在get_message()
的栈上
char msg [] = "Aren’t pointers fun?";
一旦 get_message() returns,该方法的堆栈就会被拆除。无法保证返回到 foo
的指针现在指向内存中的内容。
调用 puts() 时,堆栈可能会被修改,覆盖 "Aren't pointer's fun."
很可能调用puts
修改了堆栈并覆盖了字符串。
刚从 get_message
返回时,字符串保持不变,但被释放,即它的内存 space 可重复使用。
msg
的生命周期在从函数 get_message
返回时结束。返回的指针指向生命周期结束的对象。
访问它会产生未定义的行为。任何事情都可能发生。
在你的情况下,前者msg
的记忆似乎已经被0覆盖了。
这与 "scope" 无关。您可以通过 msg
static
来修复您的代码。这不会更改范围,但会更改其生命周期(a.k.a。存储持续时间)。
在您的 getMessage 函数中,您的消息使用的内存在堆栈上,而不是在堆上。它仍然是一个指针,只是指向堆栈上的一个位置。一旦函数 returns,堆栈改变(获取 return ip 等)意味着,虽然消息可能仍然在内存中的相同位置,但绝对不能保证。如果有任何其他东西将某些东西放到堆栈上(例如另一个函数调用),那么它很可能会被覆盖。你的留言不见了。
更好的方法是使用 malloc 动态分配内存,以确保堆中的字符串(尽管这会导致谁拥有指针并负责释放它的问题。)
如果你必须做这样的事情,我已经看到它是使用静态的:
static char * message = "I love static pointers";
编辑:尽管提到它可能仍在堆栈中,但永远不要假设它是。大多数语言甚至不允许这样做。
这里真正的问题不是,"why doesn't it work?"。问题是,"Why does the string seem to exist even after the return from get_message
, but then still not work?"
为了说明,我们再看一下main
函数,有两点注释供参考:
int main (void) {
char *foo = get_message();
/* point A */
puts(foo);
/* point B */
return 0;
}
我刚刚在 gdb 下编译 运行 这个。事实上,在 point A
,当我在 gdb 中打印出变量 foo
的值时,gdb 显示它指向字符串 "Aren’t pointers fun?"
。但是,puts
无法打印该字符串。然后,在 point B
,如果我再次在 gdb 中打印出 foo
,它就不再是原来的字符串了。
正如之前的几位评论者所解释的那样,解释是函数 get_message
将字符串留在堆栈中,而 gua运行teed 不会在堆栈中停留太久。在 get_message
return 秒之后,在调用任何其他内容之前,它仍然存在。但是当我们调用 puts
并且 puts
开始工作时,它使用堆栈的相同部分作为它自己的本地存储,所以有时在那里(并且在 puts
设法实际打印字符串),字符串被破坏。
回应 OP 的后续问题:当我们有
char *get_message() {
char msg [] = "Aren’t pointers fun?";
return msg ;
}
字符串存在于堆栈上的数组 msg
中,我们 return 指向该数组的指针,这不起作用,因为数组中的数据最终会消失。如果我们把它改成
char * msg = "Aren’t pointers fun?";
(如此微小的变化!),现在字符串存储在程序的初始化数据段中,我们 return 一个指向 that 的指针,并且因为它在程序的初始化数据段中,所以它基本上永远存在。 (是的,get_message
最终 return 看起来不同的地址这一事实很重要,尽管我不会过多地了解它是更低还是更高。)
最重要的是数组和指针是不同的。千差万别。行
char arr[] = "Hello, world!";
与看起来非常相似的线几乎没有关系
char *ptr = "Hello, world!";
现在,它们的相同之处在于您可以同时执行这两项操作
printf("%s\n", arr);
和
printf("%s\n", ptr);
但是如果你试着说
arr = "Goodbye"; /* WRONG */
你不能,因为你不能分配给一个数组。如果你想要一个新的字符串,你必须使用strcpy
,并且你必须确保新字符串的长度相同或更短:
strcpy(arr, "Goodbye");
但是如果你用指针尝试strcpy
:
strcpy(ptr, "Goodbye"); /* WRONG */
now that 不起作用,因为 ptr
指向的字符串常量是不可写的。在指针的情况下,你可以(而且经常必须)使用简单的赋值:
ptr = "Goodbye";
在这种情况下,将其设置为更长的字符串也没有问题:
ptr = "Supercalafragalisticexpialadocious";
这些是基本的区别,但正如这个问题所指出的,另一个很大的区别是数组 arr
不能有效地在函数中声明和从函数中 returned (除非你使其成为 static
),而指针 ptr
可以。
在下面的代码中,无法打印任何内容的解释是get_message()返回的指针超出范围:
char *get_message() {
char msg [] = "Aren’t pointers fun?";
return msg ;
}
int main (void) {
char *foo = get_message();
puts(foo);
return 0;
}
在gdb中运行时,发现foo位置的数据是字符串"Aren't pointers fun?":
Old value = 0x0
New value = 0x7fffffffde60 "Aren’t pointers fun?"
(这似乎与指出超出范围的指针的数据保留在内存中的答案一致),但 "puts" 的文档指出第一个数据是从给定的地址复制的:大概是 0x7fffffffde60在这种情况下。
因此:为什么没有输出?
编辑:感谢您的回答: 我 运行 原始代码在 gdb 中完成,对 puts 的调用确实更改了存储 foo 的地址处的数据。
(gdb) p foo
= 0x7fffffffde60 "Aren’t pointers fun?"
(gdb) n
11 return 0;
(gdb) p foo
= 0x7fffffffde60 "`67777"
有趣的是,当我将 change_msg() 的代码更改为:
时,代码 did 打印了消息char *get_message() {
char *msg = "Aren’t pointers fun?";
return msg ;
}
在这种情况下,foo 处的数据(地址 0x4005f4 - 较小的地址有什么意义吗?)在整个代码中保持不变。找出为什么这会改变行为会很酷
变量msg分配在get_message()
的栈上char msg [] = "Aren’t pointers fun?";
一旦 get_message() returns,该方法的堆栈就会被拆除。无法保证返回到 foo
的指针现在指向内存中的内容。
调用 puts() 时,堆栈可能会被修改,覆盖 "Aren't pointer's fun."
很可能调用puts
修改了堆栈并覆盖了字符串。
刚从 get_message
返回时,字符串保持不变,但被释放,即它的内存 space 可重复使用。
msg
的生命周期在从函数 get_message
返回时结束。返回的指针指向生命周期结束的对象。
访问它会产生未定义的行为。任何事情都可能发生。
在你的情况下,前者msg
的记忆似乎已经被0覆盖了。
这与 "scope" 无关。您可以通过 msg
static
来修复您的代码。这不会更改范围,但会更改其生命周期(a.k.a。存储持续时间)。
在您的 getMessage 函数中,您的消息使用的内存在堆栈上,而不是在堆上。它仍然是一个指针,只是指向堆栈上的一个位置。一旦函数 returns,堆栈改变(获取 return ip 等)意味着,虽然消息可能仍然在内存中的相同位置,但绝对不能保证。如果有任何其他东西将某些东西放到堆栈上(例如另一个函数调用),那么它很可能会被覆盖。你的留言不见了。
更好的方法是使用 malloc 动态分配内存,以确保堆中的字符串(尽管这会导致谁拥有指针并负责释放它的问题。)
如果你必须做这样的事情,我已经看到它是使用静态的:
static char * message = "I love static pointers";
编辑:尽管提到它可能仍在堆栈中,但永远不要假设它是。大多数语言甚至不允许这样做。
这里真正的问题不是,"why doesn't it work?"。问题是,"Why does the string seem to exist even after the return from get_message
, but then still not work?"
为了说明,我们再看一下main
函数,有两点注释供参考:
int main (void) {
char *foo = get_message();
/* point A */
puts(foo);
/* point B */
return 0;
}
我刚刚在 gdb 下编译 运行 这个。事实上,在 point A
,当我在 gdb 中打印出变量 foo
的值时,gdb 显示它指向字符串 "Aren’t pointers fun?"
。但是,puts
无法打印该字符串。然后,在 point B
,如果我再次在 gdb 中打印出 foo
,它就不再是原来的字符串了。
正如之前的几位评论者所解释的那样,解释是函数 get_message
将字符串留在堆栈中,而 gua运行teed 不会在堆栈中停留太久。在 get_message
return 秒之后,在调用任何其他内容之前,它仍然存在。但是当我们调用 puts
并且 puts
开始工作时,它使用堆栈的相同部分作为它自己的本地存储,所以有时在那里(并且在 puts
设法实际打印字符串),字符串被破坏。
回应 OP 的后续问题:当我们有
char *get_message() {
char msg [] = "Aren’t pointers fun?";
return msg ;
}
字符串存在于堆栈上的数组 msg
中,我们 return 指向该数组的指针,这不起作用,因为数组中的数据最终会消失。如果我们把它改成
char * msg = "Aren’t pointers fun?";
(如此微小的变化!),现在字符串存储在程序的初始化数据段中,我们 return 一个指向 that 的指针,并且因为它在程序的初始化数据段中,所以它基本上永远存在。 (是的,get_message
最终 return 看起来不同的地址这一事实很重要,尽管我不会过多地了解它是更低还是更高。)
最重要的是数组和指针是不同的。千差万别。行
char arr[] = "Hello, world!";
与看起来非常相似的线几乎没有关系
char *ptr = "Hello, world!";
现在,它们的相同之处在于您可以同时执行这两项操作
printf("%s\n", arr);
和
printf("%s\n", ptr);
但是如果你试着说
arr = "Goodbye"; /* WRONG */
你不能,因为你不能分配给一个数组。如果你想要一个新的字符串,你必须使用strcpy
,并且你必须确保新字符串的长度相同或更短:
strcpy(arr, "Goodbye");
但是如果你用指针尝试strcpy
:
strcpy(ptr, "Goodbye"); /* WRONG */
now that 不起作用,因为 ptr
指向的字符串常量是不可写的。在指针的情况下,你可以(而且经常必须)使用简单的赋值:
ptr = "Goodbye";
在这种情况下,将其设置为更长的字符串也没有问题:
ptr = "Supercalafragalisticexpialadocious";
这些是基本的区别,但正如这个问题所指出的,另一个很大的区别是数组 arr
不能有效地在函数中声明和从函数中 returned (除非你使其成为 static
),而指针 ptr
可以。