C语言二重指针与malloc
(内容主要源于网上,只是加入了些自己的剖析)
假设有一个二重指针:
char **p;
同时有一个指针数组
char *name[4];
如何引用p呢? 首先我们有程序代码如下
#include <stdio.h>
int main() {
char *s = "I love you";
char *s1 = "you love me";
char *s2 = "she love you";
char *s3 = "he love her";
char *name[4];
name[0] = s;
name[1] = s1;
name[2] = s2;
name[3] = s3; char **p;
p = name + 2;
printf("%d\n", *p);
printf("%d\n", **p);
printf("%c\n", **p); //为什么这个打印s
printf("%d\n", p);
printf("%d\n", *&p);
printf("%d\n", &p);
printf("%s\n", *p); //而这个打印出了字符串
return 0;
}
输出结果依次为:
4333636
115
s
1244976
1244976
1244964
she love you
分析:
1、 p是一个二重指针,它本身是一个变量。既然是变量,那么它本身就应该存储值。 这就和 int i = 65;一样,i 存储了数值 65。而p存储的是地址,地址也是一种数值。 所以当我们打印p时,打印出来的就是p中存储的地址数值,结合本题就是1244976。
2、 同理:打印*&p,我们先取p的地址(&p),p是变量,当然自身也会有存放的地址;然后我们 用*去用该地址上存储的数值,显然就是p这个变量所代表的数值,当然还是1244976。
3、 我们在分析用%d来打印*p, 为何得到了4333636。 理由是:p是二重指针,所以*p表示还是一个一重指针变量,它的具体表示可以是*(p + 0)。 为了便于理解,我们这样假设有一个 char *row; row = *(p + 0);这样我们就把问题转化 成了一维的指针了,我们打印*p,其实就是打印row,这样我们可以参考分析(1)。
4、 分析用%s来打印*p,为何得到了 she love you 其实我们完全可以利用分析(3)的思路,这时候,row代表的是这句话的首地址, 我们通过该句的首地址,来打印这个字符串。
5、 我们再来分析用%d打印**p 为何得到的是115;其实我们可以这样来看*(*(p + 0) + 0);这样的好处是可以简化,变成*(row + 0);既然row是一维 指针,那么这个(row + 0)代表的就是我们想要打印的字符串的第0个字符的地址!那么我们用* 来用这个地址的值,刚好就是's',便忘了's'对应的ACSII码就是115!
6、 分析%c 为何能打印's'。分析(5)已经全说了。
相关面试题:
错误的例程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> void Getmemery(char *p)
{
p=(char *)malloc(100);
//下面一行是自己加的
*p=1; //为了对比问题出在哪里
} void main()
{
char *str=NULL; Getmemery(str);
strcpy(str,"hello world");
printf("%s",str);
free(str);
}
编译时通过,运行时会输出段错误。
其实还是C里面经典的错误(值传递swap问题),为什么第一眼看上去是对的,因为我们一贯地认为指针传递就可以这么操作,然后忽略了后面的修改是否有效。
错就错在:传入了指针,后面做的却不是对指针的操作,而是对指针变量的操作。而对指针变量的操作又变成了原来类似值传递swap的问题,使其对指针的修改无效。指针变量与其他普通类型的变量没太大区别,都是一段内存的别称(只不过指针变量表示的内存放的是地址,其他类型的变量放的是数据),传入子函数后,只是将以str为别称的内存的值NULL地址传给了以p为别称的内存,以p为别称的内存则通过malloc成了另一块内存的别称。但是子函数结束后,回到原来的主函数,str还是原来的str,并没有发生变化,还是NULL指针。
若想要其有效只能,用指针的方式传入str(其实指针变量也是某一块内存的内容)[&str]并对指针操作(下面第2种修改方法),或者将修改后的指针变量返回(下面第1种方法)。
一种修正方法:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> char *Getmemery(void) //通过返回指针变量
{
char *p=(char *)malloc(100);
return p;
} void main()
{
char *str=NULL; str = Getmemery();
strcpy(str,"hello world");
printf("%s",str);
free(str);
}
另一种修改方法
#include <stdio.h>
#include <stdlib.h>
#include <string.h> void Getmemery(void **p) //通过使用二重指针
{
*p=(void *)malloc(100);
} void main()
{
char *str=NULL; Getmemery(&str);
strcpy(str,"hello world");
printf("%s",str);
free(str);
}
分析一下之前的程序为什么会错了。
(1)void Getmemery(char *p)
(2)char *str=NULL;
(3)Getmemery(str);
1中子程序的形参是一个指针,然后很自然会想到2,3中的调用方式,本来的想法是用malloc分配内存,然后修改传入的指针变量,那么最后就根据通过 strcpy(str,"hello world"); 就可以向分配的内存里面写数据了。一切都是那样流畅,对,因为这个用法平时用习惯了,所以根本不会去考虑正确性。
然而,这里就出问题了。首先,Getmemery(str) 传递的是 srt指针的地址,这个没有问题,C不同于C++,参数是通过传递的,而不是通过引用。也就是说,实际参数 str 先自己copy一份,然后传递给形式参数 *P接收,这个C语言的指针的时候已经强调多次了,但是自己还是错了啊,哈哈。
然后,在子程序里面,如果通过 *P 那么访问到的将是 *str的内容,这是等价的。但是,本程序一个致命的错误,非常隐蔽,那是子程序企图修改 p 的内容,而不是 *p 的内容!!这个错误找了我很久终于给揪出来了。修改了 p 的值是没有意义的,这个值是形式参数,并不会返回任何的东西,而 *p 则是通过p的地址直接访问需要的变量,这是不同的用法。所以说白了,void Getmemery(char *p) 执行之后并没有改变任何的东西,str的值并没有修改过,保持NULL,所以访问 *0 地址会被操作系统禁止,得到一个错误。
解决办法,是用2重指针。目的是要修改指针的地址,但是按照上面的分析,我们并不能去修改,但是我们可以用2重指针,将*str的地址值str,用2重指针来改变。
void Getmemery(void **p)
{
*p=(void **)malloc(100);
}
子程序修改为这个样子,出入的参数也得修改
char *str=NULL;
Getmemery(&str);
那么可以这样理解,因为形参是2重指针,所以 p 对应 &str ,*P 对应 str,之前说了,我们的目的是要修改 str的值,所以很自然,我们用 *p = xxx 这样的形式去修改了。
这样得到的程序就正确了。
malloc
标准3部曲:malloc + free +指针置空
malloc申请的是堆(heap)空间
malloc使用注意事项:
1、用malloc分配的空间之前要注意检查malloc是否分配成功(即判断返回值是地址还是NULL);
2、malloc分配完之前注意初始化,以及防止越界操作。
3、free之后注意将指针置为NULL.
/*
date:20100824
description:malloc使用规范探讨
in参数:申请的堆内存字节数,注意int,short,float需要自己乘上相应字节数。
out返回值:void *
*/
main()
{
char *str=NULL;
str=(char *)malloc(10); //注意malloc返回值是void *,申请时需要强制转换成需要的类型
memset(str,0,10); //如果不清空,申请的区域值是随机的,养成好习惯
strcpy(str,"happylife"); //使用strcpy特别注意拷贝的字符串长度<=10-1,即要预留字符串结束标志'\0'
puts(str);
free(str);
printf("str[address]:%s[%x]\n",str,str); //这里的str内容为空,但是指针地址还在 str=NULL; //注意指针free之后该指针仍然存在,最好将它指向为空。
printf("str[address]:%s[%x]\n",str,str); //这里的str内容为空,地址也为空
}
C语言二重指针与malloc的更多相关文章
- C语言中指针*p[N], (*P)[N],及**p的区别
在C语言编程中指针经常困扰着我们,但是若能灵活运用指针的话,将会使得我们编程变得更加轻松与高效.这里讲下*p[N], (*P)[N],及**p的区别,这也是之前经常困扰我的地方. 这三者的定义分别为: ...
- 为什么C/C++语言使用指针
这是参加面试时,面试官问的一道开放性题目. 问题是:为什么C/C++语言使用指针? 这个问题一问出来,直接被面试官秒杀了,面试官大神,你怎么不按套路出牌啊? 说好的malloc和new的区别呢?说好的 ...
- “对外部(局部)变量的访问”是C语言函数指针的最大弱点
1.“对外部(局部)变量的访问”是C语言函数指针的最大弱点 . #include <stdio.h> #include <stdlib.h> /* 结构体定义 */ struc ...
- C语言的指针和数组
指针和内存 指针变量也是个变量,不过保存的是另一个变量的地址.另外编译器还会记住指针所指向变量的类型,从而在指针运算时根据变量类型采取不同操作. 例如,char * a 定义了char 类型的指针变量 ...
- C语言学习之我见-malloc和free内存申请及释放函数
malloc函数负责向计算机申请确定大小的内存空间. free函数负责释放malloc的申请空间. (1)函数原型 void free(void *_Memory); void * malloc(si ...
- 浅谈c语言的指针
对于非计算机专业的同学,c语言的指针往往就是老师的一句“指针不考“就带过了.c语言的指针号称是c语言的灵魂,是c语言中最精妙的部分. 指针本质上也是变量,也就是一段内存,只是他的特殊之处是他存储的数据 ...
- C#委托与C语言函数指针及函数指针数组
C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用 ...
- C语言函数指针基础
本文写的非常详细,因为我想为初学者建立一个意识模型,来帮助他们理解函数指针的语法和基础.如果你不讨厌事无巨细,请尽情阅读吧. 函数指针虽然在语法上让人有些迷惑,但不失为一种有趣而强大的工具.本文将从C ...
- C语言的指针变量
C语言的指针变量 在C语言中,变量是固定范围的存储空间,它存储的是赋给他的值, 比如: ; /* 这里是定义一个整型变量a,并把12这个值存储在a的地址空间上 这个地址空间是系统随机分配的,对用户是透 ...
随机推荐
- Python开发入门与实战1-开发环境
1.搭建Python Django开发环境 1.1.Python运行环境安装 Python官网:http://www.python.org/ Python最新源码,二进制文档,新闻资讯等可以在Pyth ...
- <转>thinkphp的各种内部函数 D()、F()、S()、C()、L()、A()、I()详解
D.F.S.C.L.A.I 他们都在functions.php这个文件家下面我分别说明一下他们的功能 D() 加载Model类M() 加载Model类 A() 加载Action类L() 获取语言定义C ...
- php的数组与数据结构
一.数组的分类与定义 分类: 1.索引数组 $array = array(1,2,3,4,5); 2.关联数组 $array=array(1=>"aa","bb ...
- Ubuntu 14.10 下NodeJS Cannot find module 'npmlog'
在安装brunch的时候npm install -g brunch发现如下问题: Error: Cannot find module ‘npmlog’ at Function.Module._reso ...
- MAT
http://www.yrom.net/blog/2014/08/29/eclipse-mat/
- hdu 2022
Ps:麻蛋...第一次想得太复杂了..用字符串组来存.越弄越傻逼...后来用int就行了... 代码: #include "stdio.h"#include "stdli ...
- Java三大主流开源工作流引擎技术分析
首先,这个评论是我从网上,书中,搜索和整理出来的,也许有技术点上的错误点,也许理解没那么深入.但是我是秉着学习的态度加以评论,学习,希望对大家有用,进入正题! 三大主流工作流引擎:Shark,oswo ...
- STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort
最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...
- C#_控件——CheckBox,TextBox,RequiredFieldValidator
1. <asp:CheckBox ID="CheckBox2" runat="server" Text="你大爷" AutoPostB ...
- jQuery ajax传多个参数
ajax可以传送一个或多个参数到后台php中 <script> $(function(){ $("#sub_btn").click(function(){ var em ...