背后的逻辑通过指针传递给C中的指针

时间:2013-09-13 09:16:58

标签: c pointers malloc

我从这个page: 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但只找到解决方案或简短说明。任何人都可以提供更详细的帮助吗?

7 个答案:

答案 0 :(得分: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);

答案 1 :(得分: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的内容的更改不会影响传入的外部变量。当你传入单个指针时,你做的完全一样 - 你'重新为其分配一些内存的地址,但在函数退出时丢失该地址。

答案 2 :(得分:2)

为了更好地理解C中的间接级别,查看编译器如何组织其内存可能是有益的。

考虑以下示例:

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

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

这些变量将被放入call stack memory。这是一种线性的LIFO(后进先出)分配策略。

在调用function1()之前,编译器会将var1然后var2放入调用堆栈,并递增调用堆栈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
}

答案 3 :(得分: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

   }

让我们再添加一个方向:

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)
 }

答案 4 :(得分:1)

foo2的问题是传入的p仅在foo2函数内修改。这与:

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

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

在上面的代码中,由于对a的调用,bar不会更改。而是将a的副本传递给bar,并在bar中修改副本。

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

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

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

答案 5 :(得分:1)

将指针传递给值:在函数中(stack frame)创建指针的副本(即值的地址)。这允许您修改值。

将指针传递给指针:指向指针的指针的副本(即指针的地址,指针又指向该值)在函数中进行({{1 }})。这允许您修改值以及指向此值的指针。

使用stack framemalloccallocrealloc分配的内存位于new上,这意味着即使在函数返回后它也存在({ {1}}被毁坏了。

heap

但是,由于指针stack frame在返回函数后丢失,因此无法访问此内存并导致泄漏。

答案 6 :(得分:0)

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

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

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