一段神奇的 C++代码,大家觉得有没有问题? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Wangjl
V2EX    C

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

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

    如下代码,我在 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(); rturn 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 用的是指针啊,而且我调试的时候,看内存发现确实被释放掉了啊
        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 9: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     Solana     1225 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:37 PVG 07:37 LAX 16:37 JFK 19:37
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86