LV01-10-C语言-函数传参问题

本文主要是C语言基础——函数传参问题总结相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

前边在《LV01-07-C语言-函数》这篇笔记中,我们已经知道了形参有三种传递方式,地址传递,值传递和全局变量。后边学习进程和线程的时候,有遇到过通过地址传递强行完成值传递的,本篇笔记主要是想记录一些自己的疑问点。

1.变量的地址传递

我们看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void test(int *p)
{
printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
printf("%5s-->%2d--> p=%p\n", __func__, __LINE__, p);
printf("%5s-->%2d--> *p=%d\n", __func__, __LINE__, *p);
*p = 5;
}

int main(int argc, char *argv[])
{
int a = 3;
printf("%5s-->%2d--> &a=%p, a=%d\n", __func__, __LINE__, &a, a);
test(&a);
printf("%5s-->%2d--> &a=%p, a=%d\n", __func__, __LINE__, &a, a);

return 0;
}

注意函数中打印 p 的值的时候可以用 %p ,因为大多数时候指针变量中存放的是地址,都会超过一般数据类型长度,用 %p 的话可以完整的打印地址。我们编译完,运行可执行程序后,会有以下信息:

1
2
3
4
5
main-->17--> &a=0x7ffc14cfa724, a=3
test--> 7--> &p=0x7ffc14cfa6f8
test--> 8--> p=0x7ffc14cfa724
test--> 9--> *p=3
main-->19--> &a=0x7ffc14cfa724, a=5

这其实就是一个正常的变量的地址传递和变量值修改的问题,我们在传递的时候就相当于:

1
2
3
4
5
6
int *p;
p = &a;

// 或者就是

int *p = &a;
image-20220918072040078

2.变量值作为地址

我们传参的时候,要是不传地址,直接传一个 a ,又会怎样呢?因为理论上来说,我们传进去的是一个地址,地址无非就是一个很大的数罢了, a 的值不也是个数嘛,不也可以当做地址来用?我们来尝试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void test(int *p)
{
printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
printf("%5s-->%2d--> p=%p\n", __func__, __LINE__, p);
printf("%5s-->%2d--> *p=%d\n", __func__, __LINE__, *p);
*p = 5;
}

int main(int argc, char *argv[])
{
int a = 3;
printf("%5s-->%2d--> &a=%p, a=%d\n", __func__, __LINE__, &a, a);
test(a);
printf("%5s-->%2d--> &a=%p, a=%d\n", __func__, __LINE__, &a, a);

return 0;
}

我们编译的话,可能会报一些警告,但是问题不大,运行可执行程序后,会有以下信息:

1
2
3
4
 main-->17--> &a=0x7ffdb20f8194, a=3
test--> 7--> &p=0x7ffdb20f8168
test--> 8--> p=0x3
段错误 (核心已转储)

会发现,在访问 *p 的时候,报了段错误,这是为啥?由打印结果可以看出,我们传入参数的时候,传递的是 a ,我们传递的时候就相当于:

1
2
3
4
char *p;
p = a;
//或者就是
char *p = a;
image-20220918072914981

0x3 这个地址我们应该是无法访问的,自然就报了段错误,但是我们发现指针变量 p 中的值与 a 的值是一样的,这样就完成了值传递,在后边会有用到此种方法的场景。

3.字符指针问题

上边是一个变量的两种传递方式吗,会有不一样的效果,那一个字符指针传递的的时候会有什么不同之处吗,字符指针的指针变量可以指向一个字符串的首地址,指针名就可以代表这个字符串,我自己理解的就是,这个字符指针变量指向字符串的时候,及是一个指针,又代表了字符串的值,它在进行传递的时候会是怎样的呢?

3.1字符指针的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
char *str = "hello";
printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
printf("%5s-->%2d--> str=%s\n", __func__, __LINE__, str);
printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);

return 0;
}

经过编译,运行可执行程序,我们会的到下边的提示:

1
2
3
main--> 8--> &str=0x7ffc14a12830
main--> 9--> str=hello
main-->10--> *str=h

对字符指针 str 进行 *str 操作的话取到的是整个字符串的首字符,而 str 就直接代表了整个字符串。

3.2字符指针名传给函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void test(char *p)
{
printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
printf("%5s-->%2d--> p=%p\n", __func__, __LINE__, p);
printf("%5s-->%2d--> p=%s\n", __func__, __LINE__, p);
printf("%5s-->%2d--> *p=%c\n", __func__, __LINE__, *p);
printf("%5s-->%2d--> p[0]=%c\n", __func__, __LINE__, p[0]);
printf("%5s-->%2d--> &p[0]=%p\n", __func__, __LINE__, &p[0]);
}

int main(int argc, char *argv[])
{
char *str = "hello"; // 这里指向字符串常量,字符串常量无法被修改
printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
printf("%5s-->%2d--> str=%s\n", __func__, __LINE__, str);
printf("%5s-->%2d--> str=%p\n", __func__, __LINE__, str);
printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);
printf("%5s-->%2d--> str[0]=%c\n", __func__, __LINE__, str[0]);
printf("%5s-->%2d--> &str[0]=%p\n", __func__, __LINE__, &str[0]);
test(str);
return 0;
}

这样进行编译运行后,我们会得到以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
main-->18--> &str=0x7ffde7028a40
main-->19--> str=hello
main-->20--> str=0x400892
main-->21--> *str=h
main-->22--> str[0]=h
main-->23--> &str[0]=0x400892
test--> 7--> &p=0x7ffde7028a18
test--> 8--> p=0x400892
test--> 9--> p=hello
test-->10--> *p=h
test-->11--> p[0]=h
test-->12--> &p[0]=0x400892

传参的时候就相当于:

1
2
3
4
char *p;
p = str;
// 或者就是
char *p = str;

关系就如下图所示:

image-20220918084341313

3.3字符指针取地址后传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void test(char *p)
{
printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
printf("%5s-->%2d--> p=%p\n", __func__, __LINE__, p);
printf("%5s-->%2d--> p=%s\n", __func__, __LINE__, p);
printf("%5s-->%2d--> *p=%c\n", __func__, __LINE__, *p);
printf("%5s-->%2d--> p[0]=%c\n", __func__, __LINE__, p[0]);
printf("%5s-->%2d--> &p[0]=%p\n", __func__, __LINE__, &p[0]);
}

int main(int argc, char *argv[])
{
char *str = "hello"; // 这里指向字符串常量,字符串常量无法被修改
printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
printf("%5s-->%2d--> str=%s\n", __func__, __LINE__, str);
printf("%5s-->%2d--> str=%p\n", __func__, __LINE__, str);
printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);
printf("%5s-->%2d--> str[0]=%c\n", __func__, __LINE__, str[0]);
printf("%5s-->%2d--> &str[0]=%p\n", __func__, __LINE__, &str[0]);
test(&str);
return 0;
}

我们编译运行后,会得到以下输出信息:

1
2
3
4
5
6
7
8
9
10
11
12
main-->18--> &str=0x7ffe24d193d0
main-->19--> str=hello
main-->20--> str=0x400892
main-->21--> *str=h
main-->22--> str[0]=h
main-->23--> &str[0]=0x400892
test--> 7--> &p=0x7ffe24d193a8
test--> 8--> p=0x7ffe24d193d0
test--> 9--> p=@
test-->10--> *p=�
test-->11--> p[0]=�
test-->12--> &p[0]=0x7ffe24d193d0

传参的时候就相当于:

1
2
3
4
char *p;
p = &str;
// 或者
char *p = &str;

关系如下图:

image-20220918084915141