我不太明白传递给函数*mode1
或**mode2
之间的区别。
我写了一些例子。(注意:type
可以是任何类型)
[代码 1]
#include <stdio.h>
void function (type *vet)
{
/* other */
}
int main ()
{
type *vet;
function (vet)
/* other */
return 0;
}
[代码 2]
#include <stdio.h>
void function (type **vet)
{
/* other */
}
int main ()
{
type *vet;
function (&vet)
/* other */
return 0;
}
我知道:在第一种情况下是指针,在第二种情况下是指向指针的指针。但是,为什么例如在第二种情况下,如果我通过&vet
我可以在function()
中分配内存并在main()
中释放它,而在第一种情况下则不然?
我搜索一个能很好地解释我差异的人。在这两种情况下,我该怎么办?如何以及在哪里进行 malloc 或 realloc?而且免费?并在函数中修改兽医?
原始问题
主要(最显著)的区别在于是否可以更改调用函数中的值。
- 在第一个示例中,被调用函数获取调用代码中指针的副本,并且无法修改调用代码中的指针。 在第二个示例中,被调用函数
- 获取指向指针的指针,通过在被调用函数中分配给
*vet
,可以修改被调用函数中的值。
为什么在第二种情况下,如果我通过
&vet
我可以在function()
中分配内存并在main()
中释放它,而在第一种情况下则不然?
在第二种情况下,function()
中的代码可以修改main()
中的实际指针,因此main()
中vet
的值最终会得到分配的指针值,因此可以释放该值。 在第一种情况下,被调用的函数不会修改main()
中的值,因此无法通过main()
释放数据。
如何以及在哪里进行 malloc 或 realloc?而且免费?
在第一种情况下,您可以在函数中使用malloc()
或realloc()
,但您也应该在返回之前释放分配的内存,除非您的代码将值存储在全局变量中(在这种情况下,您可以委托给其他一些代码来处理free()
,但最好非常清楚哪个代码负责, 在任何情况下,使用全局变量可能不是一个好主意)。 或者,除非您更改函数签名并返回指向应由调用代码释放的已分配数据的指针。
在第二种情况下,您可以在被调用函数中分配或重新分配内存,并将其保留为由其他函数使用并由调用函数释放。
并在函数中修改兽医?
在这两个函数中,您可以根据需要修改局部vet
变量;对于任何函数的任何参数都是如此。 您不一定能做的是修改调用函数中的值;您必须在调用函数中有一个指向值的指针才能执行此操作。 在第一个函数中,您无法更改main()
中vet
的值;在第二个中,你可以。 在这两个函数中,您可以更改vet
指向的内容。(一个小问题是名称vet
在三种上下文中混为一谈——main()
和两个不同的功能。 两个函数中vet
的名称指向不同类型的事物。
扩展问题
但是,它是否像这样在
function()
中释放出来?
#include <stdio.h>
#define NUM (10)
struct example
{
/* ... */
}
void dealloc (struct example *pointer)
{
free (pointer);
}
int main()
{
struct example *e;
e = malloc (NUM * sizeof(struct example));
if (e == NULL)
return -1;
struct example *e_copy = e;
dealloc (e_copy);
return 0;
}
此代码是合法的。 将指针的值传递给dealloc()
函数;它将该指针传递给释放分配内存的free()
。dealloc()
返回后,e
和e_copy
中的指针值与以前相同,但它们不再指向分配的内存,并且对该值的任何使用都会导致未定义的行为。 可以为它们分配一个新值;旧值无法可靠地取消引用。
这有什么区别?
#include <stdio.h>
#define NUM (10)
struct example
{
/* ... */
}
int main()
{
struct example *e;
e = malloc (NUM * sizeof(struct example));
if (e == NULL)
return -1;
struct example *e_copy = e;
free (e_copy);
return 0;
}
此示例与上一个示例之间的区别在于,您直接在main()
中调用free()
,而不是从函数dealloc()
调用。
会有所作为的是:
void dealloc(struct example **eptr)
{
free(*eptr);
*eptr = 0;
}
int main()
{
...
dealloc(&e_copy);
return 0;
}
在这种情况下,dealloc()
返回后,e_copy
是一个空指针。 您可以再次将其传递给free()
,因为释放空指针是无操作。 释放非空指针两次是未定义的行为 — 它通常会导致问题,应不惜一切代价避免。 请注意,即使是现在,e
也包含最初由malloc()
返回的指针;但是使用该指针值的任何使用都会导致再次未定义的行为(但设置e = 0;
或e = NULL;
或e = e_copy;
就可以了,并且使用e = malloc(sizeof(struct example));
或类似的东西也可以。
正如你所说,第一种情况下的参数是a pointer
,并且传递了指针vet
的副本。我们可以修改vet
指向的值(例如,*vet = new value
)。但是我们不能修改指针vet
的值,因为它只是原始vet
指针的副本。因此,在第一个函数之后,*vet
的值可能会更改,但vet
的值不会更改。
那么我们如何修改指针vet
的值呢?我们使用pointer to pointer
.在第二个函数中,我们可以 为*vet
分配内存 ,这个修改后的值将保留在第二个函数之后。所以我们可以在main
中释放它.
在第一种情况下我们不能这样做,因为如果我们尝试在function
中分配内存,我们只是为指针vet
的副本分配内存,而不是原始vet
。
您的理解是正确的,即type *var;
是指向type
数据的指针,type **var;
是指向type
数据的指针。
您询问的区别,即在函数中分配内存并跟踪它,是因为能够将值分配给指针。
在 C 中,每当你想要修改函数中的值时,都必须为它提供一个指针,指示要修改的数据。
如果要分配内存,则必须知道它的位置才能使用它,然后释放它。如果您只传递指向函数的指针,并为其分配内存,则它无法更改传递的指针的值(相反,当您的程序返回到调用此分配函数的函数时,堆栈将卸载您需要的地址);它只能从中读取(在这种用法中是毫无意义的)。
考虑一下,指针变量
type *vet;
在函数堆栈上创建
main()
当从 main 调用 "function_1()" 时,将为此新函数创建一个堆栈。传递给此函数的任何参数都保存在此函数的堆栈中。在本例中,参数是指针变量。现在 function_1() 可以很好地更改此指针变量的值,但是一旦函数返回,该函数的堆栈就会被释放并且任何更改都将丢失。
但是,当您将指针传递到指针时,您传递的内容实际上是指针变量的地址,而不是指针变量。因此,当您在被调用函数内处理此指针变量时,您实际上是在处理调用函数堆栈的内存。由于此内存位于调用函数的堆栈上,因此即使在被调用函数的堆栈释放后,调用函数所做的任何更改都将保留。
首先,C 语言中没有垃圾回收器。因此,如果您不显式释放分配的内存块,它将占用内存,直到进程退出。这是内存泄漏的强大来源。
因此,一旦您使用malloc
或类似函数分配了内存块,就必须保留指向它的指针,以便有一天释放它。
如果在函数中分配一个块,并且计划在函数终止后将此块保持可用,则必须将其值传递给某个更高级别的代码段,这些代码段最终将在创建它的函数退出很久之后释放它。
为此,您有三个基本选择:
- 将指针存储在某个全局变量中
- 返回指针作为函数的结果
- 让其中一个函数参数指定指针的存储位置
案例1
void * global_address_of_buffer;
void alloc_a_buffer (int size)
{
global_address_of_buffer = malloc (size); // block reference in global var
}
alloc_a_buffer ();
// ...
free (global_address_of_buffer);
这显然是不切实际的。如果调用函数两次,将丢失第一个缓冲区的地址。
无数的例子之一,说明为什么使用全局变量会把你尖叫拖进地狱。
尽管如此,这是有可能的。
案例2
void * alloc_a_buffer (int size)
{
return malloc (size); // block reference as return value
}
void * new_buffer;
new_buffer = alloc_a_buffer (10); // retrieve pointer through return value
// ...
free (new_buffer);
这并不总是可能的。例如,您可能希望所有函数都返回指示成功或失败的状态,在这种情况下,返回值将不可用于指针。
案例3
void alloc_a_buffer (int size, void ** buffer)
{
*buffer = malloc (size); // block reference set through 2nd parameter
}
void * new_buffer;
alloc_a_buffer (10, &new_buffer); // pass pointer address to the function
// ...
free (new_buffer);