• 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
    ", *p); 
        printf("%d
    ", **p); 
        printf("%c
    ", **p); //为什么这个打印s
        printf("%d
    ", p); 
        printf("%d
    ", *&p); 
        printf("%d
    ", &p); 
        printf("%s
    ", *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,即要预留字符串结束标志''
    puts(str);
    free(str);
    printf("str[address]:%s[%x]
    ",str,str);  //这里的str内容为空,但是指针地址还在
    
    str=NULL;            //注意指针free之后该指针仍然存在,最好将它指向为空。
    printf("str[address]:%s[%x]
    ",str,str);  //这里的str内容为空,地址也为空
    }
    

      

  • 相关阅读:
    jumpserver-1.4.8安装步骤
    堡垒机使用普通用户密钥方式登陆资产,然后新创建普通用户管理资产
    使用python的subprocess模块调用linux系统命令
    MySQL数据表操作命令
    部署SonarQube代码检测服务并结合Jenkins使用
    使用Jenkins结合Gogs和SonarQube对项目代码进行测试、部署、回滚,以及使用keepalived+haproxy调度至后端tomcat
    Gogs官方帮助文档
    Centos7 用gogs搭建git仓库
    通过设置访问密码查看Tomcat服务器运行状态
    在CentOS 7系统下升级 Jenkins版本
  • 原文地址:https://www.cnblogs.com/kwseeker-bolgs/p/4588162.html
Copyright © 2020-2023  润新知