简介

师兄发来一段代码问代码是否有误。

1
2
3
4
5
6
7
8
9
10
void Malloc1(char *p, int num) {
p = (char*)malloc(num * sizeof(char));// 传进来的地址值没有用到,没有使用解引用。
}

void MyTest1() {
char *s = NULL;
Malloc1(s, 10);
strcpy(s, "hello");
printf("%s\n", s);
}

基础知识

二级指针

指针是一个变量,它的值是一个地址,地址指向为一个对应类型的变量。

赋值符号=, 左边是一个地址,右边是赋给该地址的值。变量是一个地址的别名。

&符号

int &a =b a是b的引用,是b的别名,此时&用来表示引用

单独使用时,取地址符&,用来获得一个变量的地址。&a为变量a的地址

*符号

int *a = &b a是指向b的指针,此时*表示指针变量

解引用符*,用来得到指针指向的内存地址的值。*a为a这个指针指向的对象的值。

如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void printAddr() {
int a = 2;
int *pa = &a;
int **ppa = &pa;

printf("the address of a is:%p\n", &a);
printf("the address of a is:%p, which is also the value of pointer pa\n", pa);
printf("the dereferenced value of pointer pa is:%d\n", *pa);

printf("the address of pointer pa is:%p\n", &pa);
printf("the address of pointer pa is:%p, which is also the value of pointer ppa\n", ppa);
printf("the first dereferenced value of pointer ppa is:%p, which is also the address of a\n", *ppa);
printf("the second dereferenced value of pointer ppa is:%d, which is also the value of a\n", **ppa);
}

对应输出为

1
2
3
4
5
6
7
the address of a is:000000000062FDE4
the address of a is:000000000062FDE4, which is also the value of pointer pa
the dereferenced value of pointer pa is:2
the address of pointer pa is:000000000062FDD8
the address of pointer pa is:000000000062FDD8, which is also the value of pointer ppa
the first dereferenced value of pointer ppa is:000000000062FDE4, which is also the address of a
the second dereferenced value of pointer ppa is:2, which is also the value of a

最后建议每一次指针都加一个p,每解引用一次,也就是加一个*,对应划掉一个p,也就得到对该指针解引用得到的变量值。这样只是方便快速推导,因为实际的理解比较绕。

传参的几种方式

函数被调用的时候,用实参的值来初始化形参。

1
2
3
4
5
void func1(int a){//a为形参
...
}

func1(b);//b为实参

在调用的时候可以理解为有个int a = b的过程。

按值传递

1
2
3
4
5
void func1(int a){//a为形参
...
}

func1(b);//b为实参

使用实参的值来进行初始化,形参在被调用函数中值得变化不会影响实参。

按指针传递

指针形参也是一种值的传递,只是因为传递的是一个对象的地址,因此可以通过解引用符*来对该对象直接进行修改,所以在被调用函数中可以直接根据地址来进行操作,从而使得该地址的变量被修改。

1
2
3
4
5
6
7
8
void func1(int *a){//a为形参
...
*a = 4;//使用解引用符号,修改函数外面的值。
a = 0;//将a指针保存的地址值从b的地址变成了0,实际参数没有任何改变。
}
int b = 0;
int *c = &b;
func1(b);//b为实参

指针形参,如果要修改外面的实参指向的变量值,就必须使用*来进行解引用。

按引用传递

1
2
3
4
5
6
void func1(int &a){//a为形参
...
a = 4//对形参的修改会使得实参变化
}
int b = 0;
func1(b);//b为实参

使用引用传参,可以避免值的拷贝。指针形参和引用传递,都能够修改实参的值是因为都是根据地址来进行操作的。

错误的原因

所以经过上面的复习,错误的原因就是:

1
2
3
void Malloc1(char *p, int num) {
p = (char*)malloc(num * sizeof(char));// 传进来的地址值没有用到,没有使用解引用。
}

整个函数的作用只是在形参初始的时候把s指向的地址也就是NULL,即0这个地址赋值给了p指针。之后又把p的值从0,变为了malloc函数分配的一个地址。在这个函数中只有p这个形参的指向地址发生了改变,实参s指针并未发生改变。

所以经过这个函数的调用后,s指针存放的地址依然是0,导致后面的strcpy(s, "hello");是对空指针进行操作,所以报错。

正确的写法

首先明确这段代码的意图是想让s指针这个变量的值在被调用函数中发生变化,因为需要修改实参变量的值,所以需要传入实参的地址,指向指针的地址也就是一个二级指针。所以形参应该是一个二级指针,在被调用函数中使用*来对地址进行解引用。

1
2
3
4
5
6
7
8
9
10
void Malloc(char **p, int num) {
*p = (char*)malloc(num * sizeof(char));
}

void MyTest() {
char *s = NULL;
Malloc(&s, 10);
strcpy(s, "hello");
printf("%s\n", s);
}

总结

  1. 不是语法的错误,最开始以为没有错误,是因为错误地以为指针作为形参就修改了外层实参的值。但是实际上不一定,比如不加解引用符号*则不会修改。而就算加了解引用符号,修改的也是外层实参指向的变量的值。所以一级指针无法完成修改外层指针这个变量本身的值的目的,需要使用二级指针。
  2. 修改实参的值,需要传入实参的地址。修改指针的值,需要传入指针变量的地址。