V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Wangjl
V2EX  ›  C

一段神奇的 C++代码,大家觉得有没有问题?

  •  
  •   Wangjl · 2019-04-26 16:15:01 +08:00 · 7489 次点击
    这是一个创建于 2064 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如下代码,我在 vs2015 中,for 循环 1000 遍,没有问题,10000 遍就报错

    大家觉得是哪里的问题呢?

    #include <stdio.h>
    
    void f(char* p)
    {
    	delete[] p;
    	p = new char[20000];
    }
    
    int main()
    {
    	
    	for (int i = 0; i < 10000; i++)
    	{
    		char *p = new char[2];
    		f(p);
    		delete[] p;
    	}
    	getchar();
    	return 0;
    }
    

    按理说以上代码只是在循环创建 20000 个字节的堆内存,创建了又销毁,不应该出问题才对啊。

    大家有什么看法?

    55 条回复    2019-04-28 09:50:56 +08:00
    wutiantong
        1
    wutiantong  
       2019-04-26 16:18:05 +08:00   ❤️ 12
    你 f 里面分配的 20000 字节从未释放过
    wutiantong
        2
    wutiantong  
       2019-04-26 16:18:55 +08:00   ❤️ 4
    别以为名字都叫 p 的就是同一个变量了。。。
    kizunai
        3
    kizunai  
       2019-04-26 16:19:46 +08:00 via iPhone   ❤️ 1
    楼上正解
    downdowndown30
        4
    downdowndown30  
       2019-04-26 16:20:38 +08:00 via Android   ❤️ 1
    @wutiantong 那 for 循环里的 delete 干了什么?
    lhx2008
        5
    lhx2008  
       2019-04-26 16:20:42 +08:00   ❤️ 2
    我猜一下,f 的时候指针复制了一份,所以你把 char[2]删除了,但是出了 f 函数,旧的 p 指向的地址不变
    Wangjl
        6
    Wangjl  
    OP
       2019-04-26 16:20:45 +08:00
    @wutiantong 为什么呢? 我 p 用的是指针啊,而且我调试的时候,看内存发现确实被释放掉了啊
    downdowndown30
        7
    downdowndown30  
       2019-04-26 16:21:25 +08:00 via Android   ❤️ 2
    @wutiantong 哦。。。明白了,感谢
    linxiaoziruo
        8
    linxiaoziruo  
       2019-04-26 16:21:34 +08:00   ❤️ 2
    引用传递和值传递
    superzou
        9
    superzou  
       2019-04-26 16:23:02 +08:00 via Android   ❤️ 1
    f 函数里面 p 一直都没有释放过。
    downdowndown30
        10
    downdowndown30  
       2019-04-26 16:24:35 +08:00 via Android   ❤️ 1
    @Wangjl 你看看 f 里的 p 和 for 里的 p 在栈里是不是同一个对象
    wutiantong
        11
    wutiantong  
       2019-04-26 16:25:12 +08:00   ❤️ 1
    @downdowndown30 那个 delete 操作了“野指针”,严格来说是 UB 了
    maxco292
        12
    maxco292  
       2019-04-26 16:26:51 +08:00   ❤️ 2
    正确做法:void f(char*& p)
    Wangjl
        13
    Wangjl  
    OP
       2019-04-26 16:30:31 +08:00
    搞不懂了,我 vs 里跟踪的时候,发现在 for 循环里的 p 遍历的地址,和 f 函数里,重新分配的地址是一样的。
    bb123
        14
    bb123  
       2019-04-26 16:33:11 +08:00   ❤️ 1
    1.double free
    2.值传递与指针传递
    wutiantong
        15
    wutiantong  
       2019-04-26 16:36:14 +08:00   ❤️ 1
    @Wangjl 重新分配的地址一样没什么好奇怪的,它可能会一样也可能会不一样,一样的时候就无事发生,不一样的时候程序就可能会挂掉。
    GPIO
        16
    GPIO  
       2019-04-26 16:36:55 +08:00   ❤️ 1
    值传递都是拷贝一份再操作的
    Wangjl
        17
    Wangjl  
    OP
       2019-04-26 16:39:55 +08:00
    懂了,感谢各位的回复。 这让我这个初学者难了好久,一直想不通。现在经各位指点已经想通了,应该用引用,否则会变成值传递。 造成二次释放。c++真的感觉比其他语言好难啊,坑比较多哦。
    dfjslkjdf
        18
    dfjslkjdf  
       2019-04-26 16:43:20 +08:00
    @wutiantong
    大哥好眼力
    Wangjl
        19
    Wangjl  
    OP
       2019-04-26 16:45:03 +08:00
    402124773
        20
    402124773  
       2019-04-26 17:04:44 +08:00
    学一下智能指针
    binlaten
        21
    binlaten  
       2019-04-26 17:30:31 +08:00 via Android   ❤️ 3
    @wutiantong 内存分页机制,一般 4k,4k 以内,有较大概率分到相同地址,大于 4k 这个概率就小很多了
    AngryMagikarp
        22
    AngryMagikarp  
       2019-04-26 17:36:58 +08:00   ❤️ 1
    f()里的 p 只是一个临时变量。要实现你预想的结果,需要用双重指针。如下:

    void f(char** p){
    delete[] *p;
    *p = new char[20000];
    }

    int main(){
    for (int i = 0; i < 10000; i++)
    {
    char *p = new char[2];
    f(&p);
    delete[] p;
    }
    return 0;
    }
    nonkr
        23
    nonkr  
       2019-04-26 17:42:31 +08:00 via iPhone   ❤️ 1
    你需要传入两重指针,单重指针是不能这么操作的
    Wangjl
        24
    Wangjl  
    OP
       2019-04-26 18:27:06 +08:00
    @nonkr
    @AngryMagikarp
    @wutiantong
    @bb123
    一语道破玄机, 就是这样的。 因为指针传递,相当于值传递,进去的 p 已经不是原来的 p 指针了,而是局部变量的 p,
    当在函数中释放一次后,新申请的空间实际上是给了局部变量的 p,而局部变量随着函数的销毁而销毁,因此在外部
    再次 delete 的时候,相当于进行了二次 free,所以会出问题。v 站大神真多啊,学到了。
    Wangjl
        25
    Wangjl  
    OP
       2019-04-26 18:55:34 +08:00
    解决方法就是用双重指针或者引用
    stephenyin
        26
    stephenyin  
       2019-04-26 19:17:31 +08:00
    这是一款出镜率极高的 C/C++ 基础面试题.
    dabaibai
        27
    dabaibai  
       2019-04-26 19:48:18 +08:00
    C 代码 这不是 C++
    dabaibai
        28
    dabaibai  
       2019-04-26 19:48:41 +08:00
    @dabaibai 当我没说,我看到 new delete.
    iwong0exv2
        29
    iwong0exv2  
       2019-04-26 20:21:44 +08:00 via Android
    去面华为吧!
    当年我去面的时候给了道环链表检测的题,要求实现 bool test(const LIST_ENTRY *p),p 是链表头。我上来就是 p=p->next;。面试官说我这样直接改指针,会影响外面的链表。我说这种编码风格可能不太规范,但真不会修改到外面的链表。他说这传的是指针啊,你知道指针的用法吗?
    后面当然没通过。
    GeruzoniAnsasu
        30
    GeruzoniAnsasu  
       2019-04-26 20:34:03 +08:00
    我在想 new char[2]的那个 p 被 delete 了两次为啥不会崩,1000 次 都没崩
    smdbh
        31
    smdbh  
       2019-04-26 20:39:55 +08:00
    基础了吧,不应该了
    huaouo
        32
    huaouo  
       2019-04-26 20:46:59 +08:00 via Android
    @wutiantong 那个大概叫 空悬指针?
    yippees
        33
    yippees  
       2019-04-26 22:10:19 +08:00
    1、指针的指针

    2、谁申请谁释放原则

    3、返回指针
    char* f(char* p)
    {
    if(p!=NULL)
    delete[] p;
    p = new char[20000];
    return p;
    }

    int main()
    {

    for (int i = 0; i < 100000; i++)
    {
    char *p = new char[2];
    p=f(p);
    if (p != NULL)
    delete[] p;
    }
    printf("AAA");
    getchar();
    return 0;
    }


    //和神奇无关
    hihibin
        34
    hihibin  
       2019-04-26 23:11:34 +08:00
    @Wangjl 如果是 double free,你 1000 次没问题,一万次就有问题了,说明应该不是。
    我觉得就像楼上的,需要判断是否为 NULL,如果 char *p = new char[2]; 时,分配不到内存,p=NULL,再传入 f (),
    p = new char[20000];这样就对 0 地址直接操作了,会报错吧。
    radiolover
        35
    radiolover  
       2019-04-26 23:16:59 +08:00 via Android
    国内互联网越来越水是有原因的
    jackmod
        36
    jackmod  
       2019-04-26 23:21:35 +08:00
    不要说 1000 次,1 次都不对。

    main()里的 p 分配在 main()的栈上,f()里的 p 分配在 f()的栈上。
    main()的栈和 f()的栈是两回事,所以那两个 p 就是 2 个东西。

    解释一下楼上的某个建议:f()改为 f(char**),并传入&p。
    &p 是 main()上的栈的某个位置,传给 f()后,f()的 stack 上会分配一个 char**(假设为 pp ),它指向(*pp )的位置才是 main()上的栈的某个位置,也就是 main()里的 p。
    xiaottt
        37
    xiaottt  
       2019-04-26 23:27:34 +08:00
    骗铜币的吧。。。233333333
    ipwx
        38
    ipwx  
       2019-04-26 23:29:07 +08:00
    @binlaten 你忘了 libc 的内存分配算法还有一个小内存块回收再利用的策略。
    lynskylate
        39
    lynskylate  
       2019-04-26 23:39:40 +08:00 via Android
    ....就不该这么写,同一作用域分配的内存尽量在同一作用域内 shifang
    yuikns
        40
    yuikns  
       2019-04-27 00:07:35 +08:00
    @GeruzoniAnsasu 因为是 VS。

    clang 一次直接 Abort trap。

    --

    @xiaottt 也可能是黑 VS 的?
    hihibin
        41
    hihibin  
       2019-04-27 00:37:06 +08:00
    @Wangjl 上条搞错了,习惯直接对地址操作,左值右值都搞错了。。你最后解释对的
    WANGJIEKE
        42
    WANGJIEKE  
       2019-04-27 04:19:11 +08:00
    我寻思着这不是 double delete 吗。。。double delete 在我电脑上跑一次就炸的,不知道为什么你这跑 1000 次不出问题
    missdeer
        43
    missdeer  
       2019-04-27 08:08:54 +08:00
    一楼正解
    zwh2698
        44
    zwh2698  
       2019-04-27 08:15:29 +08:00 via Android
    人生到处都是坑,且行且小心!前途未知概因不曾顿悟。
    huluhulu
        45
    huluhulu  
       2019-04-27 08:49:16 +08:00 via iPhone
    @iwong0exv2 你这个会影响。你和楼主的案例不一样。
    Wangjl
        46
    Wangjl  
    OP
       2019-04-27 10:13:52 +08:00
    这可能和 vs 有关,我现在又无法重现了,之前 for1000 次都没问题,可昨天晚上,我 for1 次就不行了。 但我做过测试,如果函数内的 p 的本身地址和函数外的 p 的地址一样的话,就不会出问题,我估计我之前那是偶然现象,可能是 vs 的问题。
    只要函数内的指针地址和外面的不一样,那一定会挂,因为二次释放。
    Wangjl
        47
    Wangjl  
    OP
       2019-04-27 10:31:15 +08:00
    #include <stdio.h>

    void f(char* p)
    {
    printf("f 函数中 p 指针本身的地址是: %x\n\n", &p);
    printf("f 函数中 p 指针里面存放的地址是: %x\n\n", *p);
    printf("释放 f 函数中 p 指针里面存放的地址 %x 指向的内存空间\n\n", *p);
    delete[] p;
    p = new char[20000];

    }

    int main()
    {

    for (int i = 0; i < 1000; i++)
    {
    char *p = new char[2];
    printf("外部 p 指针本身的地址是: %x\n\n", &p);
    printf("外部 p 指针里面存放的地址是: %x\n\n", *p);
    f(p);
    printf("释放外部 p 指针里面存放的地址 %x 指向的内存空间\n\n", *p);
    delete[] p;

    }
    getchar();
    return 0;
    }

    以上代码就可以看出原因。
    iwong0exv2
        48
    iwong0exv2  
       2019-04-27 13:59:11 +08:00 via Android
    @huluhulu 兄弟你也是花厂的?
    huluhulu
        49
    huluhulu  
       2019-04-27 15:35:33 +08:00 via iPhone
    @iwong0exv2 不是
    leido
        50
    leido  
       2019-04-27 16:10:48 +08:00 via Android
    用 f 的参数 p 指针用引用才对
    iwong0exv2
        51
    iwong0exv2  
       2019-04-27 16:14:29 +08:00 via Android
    @huluhulu 哦。如果写成 p->next=p;的话,是会修改链表,但反过来写只是利用同一个指针来做遍历,所以并不会修改链表本身。其实我这个解释有点多余,代码本身已经很清楚了。
    huluhulu
        52
    huluhulu  
       2019-04-27 16:59:13 +08:00 via iPhone
    @iwong0exv2 你是对的
    darknoll
        53
    darknoll  
       2019-04-28 09:10:06 +08:00
    char*& p
    Chenamy2017
        54
    Chenamy2017  
       2019-04-28 09:23:16 +08:00
    一楼正解,看来指针还需要再好好学习下了
    tkhmy
        55
    tkhmy  
       2019-04-28 09:50:56 +08:00 via Android
    传值,传引用,传指针好好复习一下,你这里是传值的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2889 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:45 · PVG 22:45 · LAX 06:45 · JFK 09:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.