1

我从这个页面了解到:FAQ,如果你想在一个函数中初始化一个指针,那么你应该将一个指针传递给指针,**pfoo1()

void foo1(int **p) {
    *p = malloc(100*sizeof(int)); // caller can get the memory
}

void foo2(int *p) {
    p = malloc(100*sizeof(int));  // caller cannot get the memory
}

但是,指针意味着它的值是它指向的地址。foo2()离开作用域后分配的内存去哪了?

我仍然无法弄清楚传递指向值的指针和指向指针的指针之间的不同行为?我搜索了 SO,但只找到了解决方案或简短描述。任何人都可以提供更详细的帮助吗?

4

7 回答 7

5

分配的内存foo2丢失。这会造成内存泄漏,因为您不知道foo2返回后在哪里可以找到和使用分配的内存。

考虑:

int *mymemory = NULL;
foo2(mymemory);
//mymemory is still NULL here. Memory has been allocated, 
//but you don't know at which address
//in particular, you will never be able to free() it

相对:

int *mymemory = NULL;
foo1(&mymemory);
//mymemory is now the address of the memory
//allocated by the function
dostuffwith(mymemory);
free(mymemory);
于 2013-09-13T09:19:47.650 回答
2

如果我们只从一个间接级别开始,也许会有所帮助。

考虑一下:

void foo1(int *p) {
              ^^
    //this p is local to the foo1 function
    //p contains the address of an int
    *p = 12;
    //now we dereference the pointer, so we set what p points to , to 12
 }

 void func(void) {
    int x;
        ^^
    //here is the x

    foo1(&x);
         ^^
    //now we find the location (address of) x, we copy that address
    //into the arguments for foo1()
    //foo1 sets our x int to 12

   }

让我们再添加一个indiretion:

void foo1(int **p) {
               ^^
    //this p is local to the foo1 function
    //p contains the address of a pointer to an int
    *p = NULL;
    //now we dereferenced the pointer, so we get an int*. We just
    //set it to NULL
 }

 void func(void) {
    int *x;
        ^^
    //here is the x.

    foo1(&x);
         ^^
    ///now we find the location (address of) x, we copy that address
    //into the arguments for foo1()
    //foo1() sets the x pointer to NULL. 

    }

在这两种情况下,我们都可以在 func1() 中操作 x 变量,因为 x 变量的位置(地址)被传递给 func1()。

在最后一种情况下,我们做到了*p = NULL;。这会使x == NULL. 我们可以将其设置为 malloc() 返回的内容:*p = malloc(100)

但是如果我们改变第一种情况:

 void foo1(int *p) {
               ^^
    //this p is local to the foo1 function
    //p contains the address of an int
    p = NULL; 
    //now we just set the local `p` variable to NULL.
    //the caller will not see that, since `p` is just our own copy
    //of pointer.
 }

 void func(void) {
    int x;
       ^^
    //here is the x

    foo1(&x);
    //foo1 just set its own copy of the pointer we created by doing `&x` to NULL.
    //we will not see any changes here
  }

我们只是p = NULL;在这里设置了最后一种情况。如果我们使用 malloc 代替:

void foo1(int *p) {
              ^^
    p = malloc(100); 
    //now we just set the local `p` variable to what malloc returns.
    //the caller will not see that, since `p` is just our own local copy
    //of the pointer.
    //When foo1() returns, noone has any way of knowing the location
    //of the memory buffer that malloc returned, so this memory is lost (a memory leak)
 }
于 2013-09-13T09:38:32.697 回答
2

在您的第二个示例中,分配的内存被泄漏-一旦 foo2 结束,就没有剩余的变量包含已分配的地址,因此无法释放它。

你也可以考虑

void foo3 (int bar) {
    bar = 8;
}

int main (int argc, char *argv[]) {
    int x = 0;
    foo3(x);
    printf("%d\n", x);
    return 0;
}

当 foo3 结束时,x 仍然为 0 - foo3 中 bar 内容的更改不会影响传入的外部变量。当你传入单个指针时,你所做的完全相同 - 你正在分配一些内存的地址,但在函数退出时会丢失该地址。

于 2013-09-13T09:20:31.573 回答
2

为了更好地理解 C 中的间接级别,了解编译器如何组织其内存可能会很有启发性。

考虑以下示例:

void function1 (int var1, int var2) { ... }

在这种情况下,function1 将接收 2 个变量。但是如何?

这些变量将被放入调用堆栈内存中。这是一种线性的、LIFO(后进先出)类型的分配策略。

在调用 function1() 之前,编译器会将var1thenvar2放入调用栈,并增加调用栈 ceil 的位置。然后它会调用function1()。function1() 知道它必须获取 2 个参数,因此它会在调用堆栈中找到它们。

function1() 完成会发生什么?好吧,调用堆栈递减,所有进入其中的变量都被简单地“忽略”,这几乎与“被擦除”相同。

所以很明显,无论你在 function1() 期间对这些变量做什么,调用程序都会丢失。如果必须保持调用程序可用的任何内容,则需要将其提供到可以在调用堆栈递减步骤中幸存下来的内存空间中。

请注意,对于 function1() 中的任何变量,逻辑都是相同的:在 function1() 完成后,调用函数将无法使用它。本质上,仍然存储在 function1() 内存空间中的任何结果都是“丢失”的。

有两种方法可以从函数中检索可用结果。

主要是将函数的结果保存到调用程序/函数的变量中。考虑这个例子:

int* foo3(size_t n) { return (int*) malloc(n); }

void callerFunction()
{
    int* p;
    p = foo3(100);  // p is still available after foo3 exits
}

第二个更复杂的方法是提供一个指向存在于调用内存空间中的结构的指针作为参数。

考虑这个例子:

typedef struct { int* p; } myStruct;

void foo4(myStruct* s) { s->p = (int*) malloc(100); }

void callerFunction()
{
    myStruct s;
    foo4(&s); //p is now available, inside s
}

它读起来更复杂,但也更强大。在此示例中,myStruct 包含一个指针,但结构可能要复杂得多。这打开了提供无数变量作为函数结果的视角,而不是像前面的 foo3() 示例那样仅限于基本类型。

那么当你知道你的结构实际上是一个简单的基本类型时会发生什么呢?好吧,你可以只提供一个指向它的指针。顺便说一下,指针本身就是一种基本类型。因此,如果您想获得修改后的指针的结果,您可以提供一个指向指针的指针作为参数。在那里我们找到了 foo1()。

void foo1(int **p) {
    *p = (int *) malloc(100); // caller can get the memory
}
于 2013-09-13T09:29:29.870 回答
1

问题在于传入foo2pwhich 仅在foo2函数内部进行了修改。这与:

void bar(int x)
{
   x = 42;
}

... 
    int a = 7;
    bar(a);
...

在上面的代码中,a不会因为调用bar. 相反,将 的副本a传递给bar,并在 中修改副本bar

完全相同的事情发生在foo2. 内存被分配,存储在 中p,它是传入的指针的副本。当代码返回时,原始指针保留其原始值。

通过将指针 ( &ptr) 的地址传递给foo1,我们可以修改 ORIGINAL 指针,从而将分配的地址传递回 的调用者foo1

当然,当没有对最初分配的内存的引用时,就像在调用之后的情况一样foo2,它被称为内存泄漏——通常被认为是一件坏事。

于 2013-09-13T09:24:34.277 回答
1

传递指向值的指针:在函数中(在 上)制作指针的副本(即值的地址stack frame)。这允许您修改值。

将指针传递给指针:在函数中(在 上)制作指向指针的指针(即指向值的指针的地址)的副本stack frame。这允许您修改值以及指向该值的指针。

使用malloc,和分配的内存位于 上,calloc这意味着即使在函数返回(销毁)之后它仍然存在。reallocnewheapstack frame

void foo2(int *p) {
    p = (int *) malloc(100);  // caller cannot get the memory
}

但是,由于p函数返回后指针丢失,这块内存无法访问,会导致泄漏。

于 2013-09-13T09:31:15.807 回答
0

由于所有参数的行为都与局部变量相同(它们是按值传递的),因此您不能修改按值传递的指针。

所以在foo2()你分配内存,但你不能在函数之外使用它,因为你实际上修改了局部变量。

foo()函数实际上修改了 指向的值**p,因此传递给函数的指针将被更新。

于 2013-09-13T09:24:13.487 回答