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

  • 本文关键字:背后 指针 c pointers malloc
  • 更新时间 :
  • 英文 :


我从这个页面了解到:FAQ是,如果你想初始化函数内的指针,那么你应该将指针传递给指针,即**p作为foo1()

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,但只找到了解决方案或简短描述。谁能更详细地提供帮助?

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

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

考虑一下:

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

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

你也可以考虑

void foo3 (int bar) {
    bar = 8;
}
int main (int argc, char *argv[]) {
    int x = 0;
    foo3(x);
    printf("%dn", x);
    return 0;
}

当 Foo3 结束时,x 仍为 0 - 对 FOO3 中 bar 内容的更改不会影响传入的外部变量。当您传入单个指针时,您正在执行完全相同的操作 - 您将一些内存的地址分配给它,但是当函数退出时会丢失该地址。

为了更好地理解 C 语言中的间接寻址级别,查看编译器如何组织其内存可能会有所帮助。

考虑以下示例:

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

在这种情况下,函数 1 将接收 2 个变量。但是怎么做呢?

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

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

函数 1() 完成后会发生什么?好吧,调用堆栈递减,并且所有变量都被简单地"忽略",这几乎与"被擦除"相同。

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

请注意,函数 1() 中的任何变量的逻辑都是相同的:在函数 1() 完成后,调用函数将不可用。本质上,任何仍然存储在 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
}
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 后的情况一样,这被称为内存泄漏 - 通常被认为是一件坏事。

将指针传递给值:在函数(在stack frame上)中复制指针(即值的地址)。这允许您修改值。

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

使用malloccallocreallocnew分配的内存驻留在heap上,这意味着即使在函数返回(stack frame销毁)之后它仍然存在。

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

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新