Blog Archive

Sunday, June 30, 2019

C++函数的传入参数是指针的指针(**)的详解

source: https://www.cnblogs.com/wuchanming/p/4310506.html

要修改传入参数变量的值,需要使用变量类型的指针作为参数或者变量的引用。
如果变量是一般类型的变量,例如int,则需要使用int 类型的指针int *作为参数或者int的引用类型int&。
但是如果变量类型是指针类型,例如char*,那么需要使用该类型的指针,即指向指针的指针类型 char* *,或者该类型的引用类型char*&。

首先要清楚  不管是指针还是值,传入函数后都会创建一个副本,函数结束后值内容不能传出来是因为值赋给了副本,而传入的值并没被修改;指针能传出来是因为我们修改的是指针指向的内容而不是指针指向的地址。
我们既然要举例子 就找一个比较经典的实用例子。

在我们进行内存管理的时候,若想创建一个分配空间的函数,函数中调用malloc方法申请一块内存区域。
先讲一个错误的例子,如下:
void GetMemory1(char *p,int num){
    p=malloc(sizeof(int)*num);
   return;
}

void Test1(){
      char *p=NULL;
     GetMemory(p);
}
上述例子 是很普通的 将一个指针作为参数来申请一个动态内存空间,可是这个程序是错误的。
错误的原因:
由于其中的*p实际上是Test1中p的一个副本,编译器总是要为函数的每个参数制作临时副本。在本例中,p申请了新的内存,只是把p所指向的内存地址改变了,但是Test1中p丝毫未变。因为函数GetMemory1没有返回值,因此Test1中p并不指向申请的那段内存。

因为malloc的工作机制是在堆中寻找一块可用内存区,返回指向被分配内存的指针。
所以这时p指向了这个申请的内存的地址。由于在指针作为传入参数的时候会在函数体中创建一个副本指针_p
_p指针和p指针的联系就是他们指向同一个内存区域,但是malloc的函数使得_p指向了另外一个内存区域,而这个内存区域并没有座位传出参数传给p,
所以p并没有发生任何改变,仍然为NULL。

如何才能解决上述问题呢? 使用下述办法可以解决问题:
void GetMemory2(char **p,int num){
   * p=malloc(sizeof(int)*num);
   return;
}

void Test2(){
      char *p=NULL;
     GetMemory(&p);
}

下面开始分析GetMemory2()和 Test2()的原理:
char **p 可以进行拆分(从左向右拆分)  char*    *p  ,所以可以认为*p是一个char *的指针(注意这里不是p而是*p)。那么p的内容就是一个指向char*的指针的地址,换句话来说p是指向这个char*指针的指针。
 从test2可以看出 p是一个char*的指针, &p则是这个char*指针的地址,换句话来说 &p是指向这个char*p的指针的指针,与GetMemory2()定义相符。所以在调用时候要使用&p而不是p。
在GetMemory2()中  *p=malloc(sizeof(int)*num);
其中*p保存了 这个分配的内存的地址,那么p就是指向这个分配的内存地址的指针。

其实在为什么要用指针的指针的道理很简单:
因为VC内部机制是将函数的传入参数都做一个副本,如果我们传入的用来获取malloc分配内存地址的副本变化了,而我们的参数并不会同步,除非使用函数返回值的方式才能传出去。
所以我们就要找一个不变的可以并能用来获取malloc内存地址的参数,
如果能够创建另外一个指针A,这个指针指向GetMemory1(char * p,int ...)中的传入参数指针p,那么 就算*p的内容变了,而p的地址没变,我们仍然可以通过这个指针A来对应得到p的地址并获得*p所指向的分配的内存地址,没错这个指针A就是本文想要讲的指向(char *)指针的指针(char **)。
于是我们创建了一个char *的指针*p(注意这里不是char *的指针p),这个p作为传入参数,在进入函数后,系统会为该指针创建一个副本_p,我们让*_p指向malloc分配的内存的地址(注意这里是*_p而不是_p),_p作为指向这个分配的内存地址指针的指针,这样在分配过程中_p并没有变化。

另外注意在void Test2()中的char *p 的p是指向的是malloc分配的内存的地址,通过GetMemory2()函数后&p作为传入参数没有变化被返回回来,依据&p将p指针指向了真正分配的内存空间。

最后还要注意一个要点,void Test2()中 GetMemory(&p);和void GetMemory2(char **p,int num)这个函数的定义,很容易有一个迷惑,在使用处的变量和定义处的变量是如何一一对应的。 
下面来做解释:
不对应关系:其实这两个p不是一个p,&p中的p是一个char *的指针,而char**p中的p却是指向char *指针的指针。
对应关系:其实这个对应关系就是 在void Test2()调用的时候传入参数&p是指向char *指针的指针,GetMemory2()定义中的char **p也是指向char*指针的指针,所以说 在调用时候传入的参数和在定义时候使用的传入参数必须是匹配的。

如果看了上面的文字还是比较混乱的话就用一句话来总结下重点:
1,VC的函数机制传入的参数都是会创建一个副本 不管是指针还是值;如果是值A则直接创建另外一个值B,值B拥有和A相同的值;如果是传入参指针C创建另外一个指针的副本D,那么D所指向的地址是和C相同的地址。
2,第1条总结可知指针传参比值传参的多出的功能是可以通过修改指针D所指向的地址的内容来讲函数的运算结果告知指针C,因为C和D指向相同的地址。
3,第2跳总结的指针C和D所共用的指向地址可以是值类型,也可以是另外一个指针的地址(也就是上面所讲的**)。

Dynamic memory access only works inside function & function(void **)

source: https://stackoverflow.com/questions/39486797/dynamic-memory-access-only-works-inside-function

This question is meant to be used as a canonical duplicate for this FAQ:
I am allocating data dynamically inside a function and everything works well, but only inside the function where the allocation takes place. When I attempt to use the same data outside the function, I get crashes or other unexpected program behavior.
Here is a MCVE:
#include <stdlib.h>
#include <stdio.h>

void create_array (int* data, int size)
{
  data = malloc(sizeof(*data) * size);
  for(int i=0; i<size; i++)
  {
    data[i] = i;
  }

  print_array(data, size);
}

void print_array (int* data, int size)
{
  for(int i=0; i<size; i++)
  {
    printf("%d ", data[i]);
  }
  printf("\n");
}

int main (void)
{
  int* data;
  const int size = 5;

  create_array(data, size);
  print_array(data, size);  // crash here

  free(data);
}
Whenever print_array is called from inside the create_array function, I get the expected output 0 1 2 3 4, but when I call it from main, I get a program crash.
What is the reason for this?

Asrman's notes: I corrected the syntax errors to make it successfully compiled.
#include <stdlib.h>
#include <stdio.h>
void print_array (int* data, int size)
{
  for(int i=0; i<size; i++)
  {
    printf("%d ", data[i]);
  }
  printf("\n");
}
void create_array (int* data, int size)
{
  //data = malloc(sizeof(*data) * size);
  data = (int *) malloc(sizeof(*data) * size);
  for(int i=0; i<size; i++)
  {
    data[i] = i;
  }

  print_array(data, size);
}
int main (void)
{
  int* data;
  const int size = 5;

  create_array(data, size);
  print_array(data, size);  // crash here

  free(data);
}
Answer:
The reason for this bug is that the data used by the create_array function is a local variable that only exists inside that function. The assigned memory address obtained from malloc is only stored in this local variable and never returned to the caller.

Consider this simple example:
void func (int x){
  x = 1;
  printf("%d", x);
}

...
int a;
func(a);
printf("%d", a); // bad, undefined behavior - the program might crash or print garbage
Here, a copy of the variable a is stored locally inside the function, as the parameter x. This is known as pass-by-value.
When x is modified, only that local variable gets changed. The variable a in the caller remains unchanged, and since a is not initialized, it will contain "garbage" and cannot be reliably used.

Pointers are no exception to this pass-by-value rule. In your example, the pointer variable data is passed by value to the function. The data pointer inside the function is a local copy and the assigned address from malloc is never passed back to the caller.
So the pointer variable in the caller remains uninitialized and therefore the program crashes. In addition, the create_array function has also created a memory leak, since after that function execution, there is no longer any pointer in the program keeping track of that chunk of allocated memory.

There are two ways you can modify the function to work as expected. Either by returning a copy of the local variable back to the caller:
int* create_array (int size)
{
  int* data = malloc(sizeof(*data) * size);
  for(int i=0; i<size; i++)
  {
    data[i] = i;
  }

  print_array(data, size);

  return data;
}

int main (void)
{
  int* data;
  const int size = 5;

  data = create_array(size);
  print_array(data, size);
}
or by passing the address to the caller's pointer variable and write directly to the caller variable:
void create_array (int** data, int size){
  int* tmp = (int *) malloc(sizeof(*tmp) * size);
  for(int i=0; i<size; i++)
    tmp[i] = i;  

  *data = tmp;      
  print_array(*data, size);
}

int main (void){
  int* data;
  const int size = 5;

  create_array(&data, size);
  print_array(data, size);
}
Either form is fine.
Another simple example:

void GetMemory2(char **p,int num){
   * p=malloc(sizeof(int)*num);
   return;
}

void Test2(){
      char *p=NULL;
     GetMemory2(&p);
}