Wangjl
V2EX  ›  C

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

  •  
  •   Wangjl · Apr 26, 2019 · 8883 views
    This topic created in 2749 days ago, the information mentioned may be changed or developed.

    如下代码,我在 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 replies  •  2019-04-28 09:50:56 +08:00
    wutiantong
    wutiantong
    Apr 26, 2019❤️12
    1
    你 f 里面分配的 20000 字节从未释放过
    wutiantong
    wutiantong
    Apr 26, 2019❤️4
    2
    别以为名字都叫 p 的就是同一个变量了。。。
    kizunai
    kizunai
    Apr 26, 2019 via iPhone❤️1
    3
    楼上正解
    downdowndown30
    downdowndown30
    Apr 26, 2019 via Android❤️1
    4
    @wutiantong 那 for 循环里的 delete 干了什么?
    lhx2008
    lhx2008
    Apr 26, 2019❤️2
    5
    我猜一下,f 的时候指针复制了一份,所以你把 char[2]删除了,但是出了 f 函数,旧的 p 指向的地址不变
    Wangjl
    Wangjl
    OP
    Apr 26, 2019
    6
    @wutiantong 为什么呢? 我 p 用的是指针啊,而且我调试的时候,看内存发现确实被释放掉了啊
    downdowndown30
    downdowndown30
    Apr 26, 2019 via Android❤️2
    7
    @wutiantong 哦。。。明白了,感谢
    linxiaoziruo
    linxiaoziruo
    Apr 26, 2019❤️2
    8
    引用传递和值传递
    superzou
    superzou
    Apr 26, 2019 via Android❤️1
    9
    f 函数里面 p 一直都没有释放过。
    downdowndown30
    downdowndown30
    Apr 26, 2019 via Android❤️1
    10
    @Wangjl 你看看 f 里的 p 和 for 里的 p 在栈里是不是同一个对象
    wutiantong
    wutiantong
    Apr 26, 2019❤️1
    11
    @downdowndown30 那个 delete 操作了“野指针”,严格来说是 UB 了
    maxco292
    maxco292
    Apr 26, 2019❤️2
    12
    正确做法:void f(char*& p)
    Wangjl
    Wangjl
    OP
    Apr 26, 2019
    13
    搞不懂了,我 vs 里跟踪的时候,发现在 for 循环里的 p 遍历的地址,和 f 函数里,重新分配的地址是一样的。
    bb123
    bb123
    Apr 26, 2019❤️1
    14
    1.double free
    2.值传递与指针传递
    wutiantong
    wutiantong
    Apr 26, 2019❤️1
    15
    @Wangjl 重新分配的地址一样没什么好奇怪的,它可能会一样也可能会不一样,一样的时候就无事发生,不一样的时候程序就可能会挂掉。
    GPIO
    GPIO
    Apr 26, 2019❤️1
    16
    值传递都是拷贝一份再操作的
    Wangjl
    Wangjl
    OP
    Apr 26, 2019
    17
    懂了,感谢各位的回复。 这让我这个初学者难了好久,一直想不通。现在经各位指点已经想通了,应该用引用,否则会变成值传递。 造成二次释放。c++真的感觉比其他语言好难啊,坑比较多哦。
    dfjslkjdf
    dfjslkjdf
    Apr 26, 2019
    18
    @wutiantong
    大哥好眼力
    402124773
    402124773
    Apr 26, 2019
    20
    学一下智能指针
    binlaten
    binlaten
    Apr 26, 2019 via Android❤️3
    21
    @wutiantong 内存分页机制,一般 4k,4k 以内,有较大概率分到相同地址,大于 4k 这个概率就小很多了
    AngryMagikarp
    AngryMagikarp
    Apr 26, 2019❤️1
    22
    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
    nonkr
    Apr 26, 2019 via iPhone❤️1
    23
    你需要传入两重指针,单重指针是不能这么操作的
    Wangjl
    Wangjl
    OP
    Apr 26, 2019
    24
    @nonkr
    @AngryMagikarp
    @wutiantong
    @bb123
    一语道破玄机, 就是这样的。 因为指针传递,相当于值传递,进去的 p 已经不是原来的 p 指针了,而是局部变量的 p,
    当在函数中释放一次后,新申请的空间实际上是给了局部变量的 p,而局部变量随着函数的销毁而销毁,因此在外部
    再次 delete 的时候,相当于进行了二次 free,所以会出问题。v 站大神真多啊,学到了。
    Wangjl
    Wangjl
    OP
    Apr 26, 2019
    25
    解决方法就是用双重指针或者引用
    stephenyin
    stephenyin
    Apr 26, 2019
    26
    这是一款出镜率极高的 C/C++ 基础面试题.
    dabaibai
    dabaibai
    Apr 26, 2019
    27
    C 代码 这不是 C++
    dabaibai
    dabaibai
    Apr 26, 2019
    28
    @dabaibai 当我没说,我看到 new delete.
    iwong0exv2
    iwong0exv2
    Apr 26, 2019 via Android
    29
    去面华为吧!
    当年我去面的时候给了道环链表检测的题,要求实现 bool test(const LIST_ENTRY *p),p 是链表头。我上来就是 p=p->next;。面试官说我这样直接改指针,会影响外面的链表。我说这种编码风格可能不太规范,但真不会修改到外面的链表。他说这传的是指针啊,你知道指针的用法吗?
    后面当然没通过。
    GeruzoniAnsasu
    GeruzoniAnsasu
    Apr 26, 2019
    30
    我在想 new char[2]的那个 p 被 delete 了两次为啥不会崩,1000 次 都没崩
    smdbh
    smdbh
    Apr 26, 2019
    31
    基础了吧,不应该了
    huaouo
    huaouo
    Apr 26, 2019 via Android
    32
    @wutiantong 那个大概叫 空悬指针?
    yippees
    yippees
    Apr 26, 2019
    33
    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
    hihibin
    Apr 26, 2019
    34
    @Wangjl 如果是 double free,你 1000 次没问题,一万次就有问题了,说明应该不是。
    我觉得就像楼上的,需要判断是否为 NULL,如果 char *p = new char[2]; 时,分配不到内存,p=NULL,再传入 f (),
    p = new char[20000];这样就对 0 地址直接操作了,会报错吧。
    radiolover
    radiolover
    Apr 26, 2019 via Android
    35
    国内互联网越来越水是有原因的
    jackmod
    jackmod
    Apr 26, 2019
    36
    不要说 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
    xiaottt
    Apr 26, 2019
    37
    骗铜币的吧。。。233333333
    ipwx
    ipwx
    Apr 26, 2019
    38
    @binlaten 你忘了 libc 的内存分配算法还有一个小内存块回收再利用的策略。
    lynskylate
    lynskylate
    Apr 26, 2019 via Android
    39
    ....就不该这么写,同一作用域分配的内存尽量在同一作用域内 shifang
    yuikns
    yuikns
    Apr 27, 2019
    40
    @GeruzoniAnsasu 因为是 VS。

    clang 一次直接 Abort trap。

    --

    @xiaottt 也可能是黑 VS 的?
    hihibin
    hihibin
    Apr 27, 2019
    41
    @Wangjl 上条搞错了,习惯直接对地址操作,左值右值都搞错了。。你最后解释对的
    WANGJIEKE
    WANGJIEKE
    Apr 27, 2019
    42
    我寻思着这不是 double delete 吗。。。double delete 在我电脑上跑一次就炸的,不知道为什么你这跑 1000 次不出问题
    missdeer
    missdeer
    Apr 27, 2019
    43
    一楼正解
    zwh2698
    zwh2698
    Apr 27, 2019 via Android
    44
    人生到处都是坑,且行且小心!前途未知概因不曾顿悟。
    huluhulu
    huluhulu
    Apr 27, 2019 via iPhone
    45
    @iwong0exv2 你这个会影响。你和楼主的案例不一样。
    Wangjl
    Wangjl
    OP
    Apr 27, 2019
    46
    这可能和 vs 有关,我现在又无法重现了,之前 for1000 次都没问题,可昨天晚上,我 for1 次就不行了。 但我做过测试,如果函数内的 p 的本身地址和函数外的 p 的地址一样的话,就不会出问题,我估计我之前那是偶然现象,可能是 vs 的问题。
    只要函数内的指针地址和外面的不一样,那一定会挂,因为二次释放。
    Wangjl
    Wangjl
    OP
    Apr 27, 2019
    47
    #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
    iwong0exv2
    Apr 27, 2019 via Android
    48
    @huluhulu 兄弟你也是花厂的?
    huluhulu
    huluhulu
    Apr 27, 2019 via iPhone
    49
    @iwong0exv2 不是
    leido
    leido
    Apr 27, 2019 via Android
    50
    用 f 的参数 p 指针用引用才对
    iwong0exv2
    iwong0exv2
    Apr 27, 2019 via Android
    51
    @huluhulu 哦。如果写成 p->next=p;的话,是会修改链表,但反过来写只是利用同一个指针来做遍历,所以并不会修改链表本身。其实我这个解释有点多余,代码本身已经很清楚了。
    huluhulu
    huluhulu
    Apr 27, 2019 via iPhone
    52
    @iwong0exv2 你是对的
    darknoll
    darknoll
    Apr 28, 2019
    53
    char*& p
    Chenamy2017
    Chenamy2017
    Apr 28, 2019
    54
    一楼正解,看来指针还需要再好好学习下了
    tkhmy
    tkhmy
    Apr 28, 2019 via Android
    55
    传值,传引用,传指针好好复习一下,你这里是传值的
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2914 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 105ms · UTC 12:38 · PVG 20:38 · LAX 04:38 · JFK 07:38
    ♥ Do have faith in what you're doing.