当指针在作用域内创建时,当指针超出作用域时所指向的变量会发生什么?

7
标题已经说明了一切。 我发现一个旧的问题基本上是相同的, 但是我需要更进一步的澄清。
在这个问题中,被接受的答案说:
char* text = "Hello, world"; 

Here an automatic variable (a pointer) is created on the stack and set to point to a value in constant memory, which means:

  • the string literal in "" exists through the whole program execution.
  • you are not responsible for "allocating" or "freeing" it
  • you may not change it. If you want to change it, then you have to allocate some "non-constant memory" and copy it there.
这是否意味着指针被删除,但指针所指向的数据不会被删除? 如果我在函数中创建1,000,000个字符指针,在它们超出范围时,所有内存都会被释放吗?还是只释放制作指针所需的内存,留下实际的字符本身来占用所有内存?

指针并不会被删除。它会被销毁,但对于指针来说,这并没有任何作用。 - Pete Becker
4个回答

10

字符数组会在程序的整个执行期间一直存在,因为它们具有静态存储期。这并不意味着你需要将它们删除 - 它们应该在整个程序的执行期间保持存在。事实上,对其调用delete将导致未定义的行为。你只能delete使用new分配的内容。

指针本身具有自动存储期,并在超出其作用域时被销毁。值得注意的是,指针必须是const char*类型,因为字符串字面量给出的是一个const char数组。例如:

void func()
{
  const char* str = "Hello";
}

包含字符数组Hello\0的内容在程序运行期间存在。指针str仅存在于该函数的持续时间内。这里不需要删除任何东西。

如果你仔细想一下,这就很有意义了。所有你在源代码中编写的字符串都必须存在于可执行文件中的某个地方。编译器通常将这些字符串写入可执行文件的数据段中。当你运行程序时,可执行文件与包含字符串的数据段一起加载到内存中。

如果你的程序中有两个具有相同或重叠文本的字符串文字,则编译器可以优化它们,只存储其中一个。例如:

void func()
{
  const char* str1 = "Hello";
  const char* str2 = "Hello";
  const char* str3 = "lo";
}
编译器只需将字符Hello\0写入可执行文件一次。前两个指针将仅指向H,第三个指针将指向第二个l。你的编译器可以进行此类优化。当然,对于这个示例,编译器可以通过完全摆脱字符串来进一步优化 - 它们在任何有助于程序的可观察行为方面都没有用处。

所以,是的,如果您有一百万个不同的字符串文字,它们以某种方式对程序的可观察行为做出贡献,那么它们当然必须作为可执行文件的一部分存在。

剪去我的问题的含糊,我在其中一个类中有一个函数创建了一个指针 unsigned char* imageData;,随后将其传递到另一个函数中,并返回数据。当完成所有必要的与imageData相关的操作后,如果我没有显式删除它,那么指针将超出范围并被释放,但它所指向的所有数据都将保留下来吗? - xcdemon05
这取决于内存是如何分配的。字符串字面值具有静态存储期。您无法delete它们。这并不意味着当指针超出范围时字符串字面值会自动销毁 - 它只是不会被销毁!但是,如果您通过执行诸如new unsigned char[100]之类的操作来分配imageData指向的数组,则该数组具有动态存储期,必须进行delete[],否则将导致内存泄漏。黄金法则:只删除您new/new[]的内容! - Joseph Mansfield

2
很抱歉,你的示例不足以涵盖完整情况。
首先,需要解释一些简单的临时词汇:一个“内存单元”是指具有给定大小的内存区域(在C++中),它包含一个“值”。多个内存单元可能包含相同的值,这并不重要。
您应该考虑三种类型的“内存单元”:
- “Hello, World!”:此内存单元具有静态存储期,它存在于程序的整个运行期间。 - `void foo(int a);` 和 `void foo() { int a = 5; }`:在这两种情况下,内存单元a都具有自动存储期,当函数foo返回时,它将自动“消失”。 - `void foo() { int* a = new 5; }`:创建一个匿名内存单元以“某种方式”存储值5,并创建一个具有自动存储期的内存单元a以存储匿名内存单元的地址。
所以,“指针”何时会超出作用域(“消失”)?
好吧,就是这样。指针消失了。最具体地说,它所指向的“内存单元”没有发生任何特殊的事情。
void foo(int a) {
    int* pointer = &a;
} // pointer disappears, `a` still exists briefly

void foo() {
    int* pointer = 0;
    {
        int a;
        pointer = &a;
    } // a disappears, pointer's value does not change...
} // pointer disappears

实际上,在C和C++中:

  • 您可以保留不再存在的对象的地址 => 悬空引用
  • 您可能会丢失现有对象的所有地址 => 内存泄漏

那么当char const* text = "Hello, world"; 中的text超出范围时会发生什么?

没有任何事情发生。


2
我认为如果这已经足够让SO认为它是一个答案,那么我会回答“没有什么”(回答标题)。
至于你的百万指针到字符,虽然指针将被弹出(尽管你必须有相当大的堆栈来容纳百万个指针),但它们所指向的数据将在你的记忆中挥之不去。

明白了,所以删除它们是必须的。 - xcdemon05
@xcdemon05 不不不,我认为Michael所说的“haunt your memories”是指“只会存在于你的记忆中”。 - djechlin
@xcdemon05,无论你是否删除它们都取决于情况。请看我的回答,希望能解释清楚这一点。 - djechlin
我的意思取决于它如何被分配。实际上,我确实是指“将占用内存”,尽管我假设所指向的数据先前已经被分配。严格来说,无论它指向什么,只要它在其他地方没有被删除,这都是正确的。只是如果它没有被分配,那么占用内存就没问题了;-) - Michael Krelin - hacker

1
指针本身占用“自动存储器”(通常是堆栈)上的空间。一旦函数返回(或作用域结束,但技术上来说,几乎所有编译器都会“等待”函数返回之前释放空间),它就会被删除。

如果您在循环中调用同一函数1百万次,则任何给定时间只有一个指针。如果您有100万个函数[和大量内存],则当前调用的每个函数都将有一个指针。例如:

char *foo()
{
    char *text1 = "Hello";
    return text1;
}

void bar()
{
    char *text2 = "World!";
    printf("%s %s!\n", foo(), text2);
}


void baz()
{
    char *text3 = "Meh";
    bar();
}

int main()
{
    char *text4 = "Main";

    baz();
}

当我们进入main函数时,text4被创建在栈上——它被初始化为字符串“Main”,该字符串存储在其他一些内存位中。当我们调用baz()时,text3被创建并初始化为“Meh”,然后调用bar()创建text2并指向文本“World”,再调用foo()创建text1并初始化为Hello。当foo()返回时,text1内的地址作为返回值给出,指针本身消失。当printf()完成后,bar()返回,指针也消失。

字符串“Main”、“Meh”、“Hello”和“World”仍然保留在程序运行期间。


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