上一节中我们讲了指针的最基本的概念,目的是让大家对指针有个最基本的认识。接下来我们会通过一些具体的练习题帮大家一点点搞清楚指针相关的全部知识点。
指针的移动
我们先看一下这段代码。
int main()
{
char* p1 = "China";
char *p2;
p2 = (char*)malloc(10);
memset(p2, 0, 10);
while (*p2++ = *p1++);
printf("%s\n", p2);
return 0;
}
这段代码的目的是为了把指针p1指向的字符串拷贝到指针p2中。但运行结果却为空。接下来我们就看看这段代码究竟犯了哪些错误。
1. 代码的对称性
前面讲过,函数malloc的作用是在内存的堆中申请一段空间,我们可以用指针去管理这段空间的地址。
p2 = (char*)malloc(10);
这句代码实现了从堆中申请了一段10个Bytes的空间,指针p2指向这段空间的首地址。
memset(p2, 0, 10);
之后,又用memset这个函数将这10个Bytes的空间内容全部初始化为零。这两句话没有任何问题。
但这里需要注意的是,malloc申请了空间之后,如果在使用完之后不调用free函数释放这段空间,它会一直被认为是正在使用中,其它任何程序都不能使用。如果每次申请空间都不释放的话,就会有内存很快被占满的可能。这是C语言程序设计中的大忌。
因此,我们需要在程序中加入这句话:
free(p2);
理论上讲,程序中没出现一个malloc,就需要在申请的这段空间的作用域最后有一个free。这就是程序设计中的对称性。一些静态代码检查工具就是根据这个来初步判断是否可能出现内存溢出。
2. 指针偏移
加了free(p2)是不是就正确了呢?其实还有问题。最开始,内存中如图所示:
指针p1指向栈中的一段空间的首地址,指针p2指向了堆中的一段空间的首地址。而在执行while循环结束后,内存中的情况成了这样:
字符串已经拷贝完成,但问题是两个指针都改变了之前的位置。那么问题就来了:
- p2已经不是之前申请的那段10个Bytes的空间的首地址了,此时执行free(p2)语句无法实现释放内存的目的
- p2此时指向的位置是'\0',在用printf输出时,系统会认为是一个空字符串,因此什么都打印不出来
3. 正确代码
那么,究竟要如何修改才能正确实现功能呢。请看下面这段代码。
int main()
{
char* p1 = "China";
char *p2, *p3;
p2 = (char*)malloc(20);
memset(p2, 0, 20);
p3 = p2;
while (*p3++ = *p1++);
printf("%s\n", p2);
free(p2);
return 0;
}
我们又多声明了一个指针p3,让它也指向p2的空间,于是在循环结束时,内存中是这样的:
此时再去打印和释放p2都不会有任何问题了。
指针使用的最大灵活性就是它的移动,通过这种移动可以省去复杂的计算偏移的工作,然而在移动的过程中一定要记录下最初的地址,否则很容易出现问题。
打印内存地址
在调试程序过程中,特别是复杂内存操作的程序,打印内存地址是个非常重要调试方法。那如何能够通过Printf函数将内存地址打印在屏幕上呢?请看下面这段代码:
int main()
{
int a;
printf("%p\n", &a);
printf("0x%x\n", &a);
return 0;
}
这段代码的功能是打印出变量a的地址。请大家自己试验查看结果。
在不同的环境中,这段程序的执行结果可能略有不同。在标准C语言中,%p打印出的地址前面会有"0X",而%x打印出的地址没有。因此我们这里用了“0x%x”。
今天就到这里,下一篇我们讲继续讲解指针相关的例题。
我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。
上一篇:C语言从零开始(十六)-指针1
下一篇:C语言从零开始(十八)-指针3