本地指针存储在内存的哪里?

6

我是C语言的初学者。我有一个如下的程序:

int main()
{
    char* func();
    char *c;
    c = func();
    printf("%s", c);
}

char* func()
{
    char *ptr = "OK";
    return ptr;
}

众所周知,ptrfunc()的局部变量并且是一个指针。作用域是局部的。 但是当ptr被返回给调用者main时,它仍然有效,并且当打印c时,会输出"OK"。
这是怎么可能的?ptr存储在哪个内存段中:堆栈还是堆?
8个回答

9

当使用指针时,底层发生了什么(请注意,指针就像其名称所示,类似于指向某物的手指)。

让我们逐步分解您的代码并逐个理解它。在您的主函数中,声明了一个字符指针 *c。

char *c;

然后你做了这个:

c = func();

这个函数告诉C指针指向func()指向的任何内容。

让我们来看一下你的函数

char* func()
{
    char *ptr = "OK";
    return ptr;
}

第一行再次声明了一个字符指针ptr并将"OK"赋值给那个地址(注意:指针只是指向某个内存地址)。所以现在,那个地址包含字符串“OK”(或更准确地说,是一个字符数组)。
然后你返回ptr,它是“OK”所在位置的地址。请注意,因为ptr在func()中声明,它是一个局部变量。因此,一旦返回,它就会从堆栈中被删除(噼里啪啦!消失了)。
然而,由于在你的main()函数中:
c = func(); 

变量c指向存储“OK”位置的地址。因此,即使变量ptr不再存在,变量c仍然知道它在哪里,并且仍然可以访问“OK”。
回答你的问题:
- ptr存储在堆栈中 - 当你退出func()函数时,ptr从堆栈中弹出

4

让我们暂时不谈堆栈。让我们谈论C语言。

局部变量ptrfunc外部不存在,因此调用者不能且不应该引用它。

但是C语言不支持传递引用。一切都通过值传递。 func返回ptr存储的给它的调用者。就像你会返回一个int或其他类型的一样。

所以对于你的例子,ptr的值是称为"OK"的字符串字面值的地址。而字符串字面值在整个程序范围内都有效。因此,func返回一个字符串字面值的地址,可以在程序的任何地方进行解引用。

更明确地说,func等同于:

const char * func (void)
{
  return "OK";
}

这就是为什么一切都会顺利进行的原因。
还有需要了解的是,虽然以下内容可以接受:
const char * func (void)
{
  char *ptr = "OK";
  return ptr;
}

这不是:

const char * func (void)
{
  char ptr[] = "OK";
  return ptr;
}

现在你返回的是一个指向局部变量func的数组指针,所以这样做不好。

另外还有一点小问题。应该这么写:const char * func(), 因为"OK"不能被修改。


3

当您调用函数时,所有局部变量和返回地址都会推入堆栈。因此,指针将在堆栈上,但“OK”字符串位于常量数据部分。当您从func返回时,ptr的值被分配给c变量,ptr不再存在(实际上是存在的),但其值存储在c中。


C标准并没有使用“stack”这个术语。 - user411313
我同意你的观点。但是我的疑问是,当函数返回时,指针的作用域随着函数的结束而结束。那么它怎么还有效呢?这就是我不理解的地方。 - ravi ravi
例如,当从函数返回时,返回值可能存储在eax寄存器中。 - flamingo

2

文本"OK"存储在您的可执行文件的常量数据部分中(当您的进程启动时加载到内存中)。虽然ptr是一个局部变量,但编译器会将其初始化为指向存储在其他地方的"OK"。当您将ptr返回给调用者时,指针仍然指向相同的"OK"


C标准不使用术语“常量数据段”。 - user411313
虽然这是真的,但使用一个名称比不使用名称更有帮助。 - Greg Hewgill
字符串字面量是一种具有静态存储期的char数组类型的只读对象。编译器如何实现这个只读属性(作为/在“常量数据段”,“只读数据段”或其他...)是实现定义的,因此不是主要问题。 - user411313

1
如果您指的是字符串OK存储的位置,那么它存储在内存的代码段中,而ptr存储在堆栈中。 func()返回的地址仍然可以访问OK在代码段中的位置。
此外,代码段是只读的。这就是为什么其他答案建议将函数声明为:的原因。
const char * func ()

这确保了返回地址所指向的值是不可修改的。(指向常量的指针)

0

char *ptr = "OK"; - 这个语句将为变量ptr分配4个字节(如果是32位机器),并且它将保存字符串文字OK的地址。现在,ptr具有一个字符串OK的起始地址(4个字节),该字符串实际上包括\0在内的3个字节。这个3个字节的字符串OK将在文本段中存在,这是只读数据。您也无法修改。例如,在这种情况下,ptr[0] = 'T'是不可能的。文本段中的字符串文字(3个字节)将在进程的整个生命周期中存在。但是一旦控制权从函数func返回,则为变量ptr分配的4个字节以保存字符串文字的地址将被释放。您也可以像下面这样编写您的函数func

char* func()
{
    return "OK";
}

将您的函数修改如下

char* func()
{
    char ptr[] = "OK";
    return ptr;
}

这次将分配3个字节的变量ptr来本地存储字符串。其作用域仅限于函数内部。

0

堆栈上的ptr本身是瞬时的,但它所指向的地方不是。一种情况是在您的func()中,字符串文字存储在“常量数据区”,另一种情况是通过malloc()分配在堆上的数据,因此是全局的,在函数的堆栈消失后不会被销毁。

char* func()
{
        char* ptr;
        ptr = (char*)malloc(100);
        strcpy(ptr, "OK");
        return ptr;
}

0

ptr 保存的是常量数据段中 "OK" 数据的地址。这就是被返回的地址,而不是 ptr 的地址。因此,即使 ptr 不再存在,"OK" 仍然保持不变。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接