【问题标题】:In c program, why free memory of string (which copy from strcpy) in osx is not woking? [duplicate]在 c 程序中,为什么 osx 中的字符串(从 strcpy 复制)的空闲内存不工作? [复制]
【发布时间】:2017-05-09 05:05:48
【问题描述】:

我的 osx 版本是 10.12,

当我从原始字符串复制字符串,然后释放它,它不工作,

但是在linux(centos 6.2)下没问题,我打印的副本无效。

找不到原因,真的很困惑。

代码如下:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
 char *copy;
 char *origin = "tutorialspoint";
 copy = (char *) malloc(15);
 strcpy(copy, origin);

 free(copy); //not working
 printf("string = %s,  Address = %u\n", copy, copy);
 printf("origin = %s,  Address = %u\n", origin, origin);
 return(0);
}

最奇怪的是当我为origin释放内存时,副本的内存也被释放了

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
 char *copy;
 char *origin = "tutorialspoint";
 copy = (char *) malloc(15);
 strcpy(copy, origin);

 free(origin); //working for copy too 
 printf("string = %s,  Address = %u\n", copy, copy);
 printf("origin = %s,  Address = %u\n", origin, origin);
 return(0);
}

【问题讨论】:

  • 这个程序中有很多未定义的行为。
  • 代码非常错误,但它并不是真正的重复。答案需要更进一步。

标签: c macos memory


【解决方案1】:

您的问题是free(copy) 的位置,在您使用copy 之后移动它,否则您正试图从已经是freed 的变量中读取(未初始化的value - 调用 Undefined Behavior),(正如@Pras 正确指出您 free (copy),而不是 free (origin),例如

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
    char *copy;
    char *origin = "tutorialspoint";
    copy = malloc (strlen (origin) + 1);
    strcpy(copy, origin);

    printf("string = %s,  Address = %p\n", copy, (void *)copy);
    printf("origin = %s,  Address = %p\n", origin, (void *)origin);
    free(copy);  /* works find here */
    return(0);
}

注意:使用%p 作为指针地址。

您还应该验证所有内存分配,例如

    if (!(copy = malloc (strlen (origin) + 1))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

使用/输出示例

$ ./bin/origin
string = tutorialspoint,  Address = 0xbe3010
origin = tutorialspoint,  Address = 0x400710

副本被释放

继续您的评论,copy 实际上已被释放。你怎么知道?你通过一个内存/错误检查程序运行你的代码,比如valgrind,例如

$ valgrind ./bin/origin
==10564== Memcheck, a memory error detector
==10564== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10564== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==10564== Command: ./bin/origin
==10564==
string = tutorialspoint,  Address = 0x51d8040
origin = tutorialspoint,  Address = 0x400710
==10564==
==10564== HEAP SUMMARY:
==10564==     in use at exit: 0 bytes in 0 blocks
==10564==   total heap usage: 1 allocs, 1 frees, 15 bytes allocated
==10564==
==10564== All heap blocks were freed -- no leaks are possible
==10564==
==10564== For counts of detected and suppressed errors, rerun with: -v
==10564== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

如您所见,所有分配的内存实际上都已被释放——不可能发生泄漏,并且 0 个上下文中出现了 0 个错误。

【讨论】:

  • copy 仍然没有在 osx 中释放
  • 不,还没有,您在 printf("string = %s, Address = %p\n", copy, (void *)copy); 之后调用 free,或者您在代码中调用 Undefined Behavior 违反了禁止尝试从具有被释放。任何事情都可能发生,SegFault、奇怪的值、正常值——它的未定义。 (您可以将复制设置为NULL 并重新分配,但在free 和任何后续reallocation 之间,您无法从copy 读取。)
  • 调用free - free 后数据似乎有效是完全正常的,只是告诉分配器它可以重用该块,为什么还要费心覆盖旧数据?下次有人(例如)执行类似大小的malloc 时,任何获得该内存块的人都会将其覆盖。
  • 是的,但这仍然是未定义的行为free 释放的内存尚未重新分配或以其他方式使用的事实不会改变这一事实。您可以访问分配的内存块中的值只要该内存块保持有效,您就无法控制free 之后发生的事情。这只是规则......
  • @DavidCRankin 当然,我只是向 OP 解释他所看到的,因为内存仍然可以访问的事实似乎是他最困扰的事情。
【解决方案2】:

在你的程序中,源不是在堆上分配的,所以释放它会导致未定义的行为

【讨论】:

    【解决方案3】:

    在许多情况下,运行第一个示例仍会为copyorigin 打印出tutorialspoint 字符串。 free() 不会修改您 malloc'ed 位置中的内存,它也不会更改指向原点的指针的地址,因此理论上您仍然可以通过打印出 copy 看到该值。但是,你不应该依赖这个。在运行free() 之后,您应该认为指针在重新分配之前不可用,因为另一个进程可能会覆盖该内存空间。

    第二个示例出于类似原因打印,但origin 是一个堆指针,不能被释放。只有 malloc 的指针可以使用 free() 安全释放

    【讨论】:

    • 正如我在之前的评论中所写,“...您正在尝试定义 未定义的行为,这是...不是一个好主意除非你在谈论一个特定的实现,在这种情况下你不是在谈论C,而是C的一个子集,它以一种更严格和不可移植的方式表现。”跨度>
    • @Seb 是的,我已将拼写错误修正为引用 copy。我想我注意到它不可靠/不便携,但最初的问题也特定于特定的操作系统,这也是我描述的场景。尽管 free 的规范未定义它,但它在单个操作系统上非常可重现和可预测,因此描述它只是 undefined 无法解释为什么它可能会在业余爱好者看来它没有做任何事情全部。
    • 对于为该操作系统生成的每个硬件、编译器和标准库版本,是否在单个操作系统上非常可重现和可预测,以及可用的编译器标志和优化的每个排列?这是大量的测试,需要粗体声明;我怀疑你陈述的真实性。例如,我认为 LLVM/ClangGCC 可能会证明你错了,如果你使用他们的地址清理程序(链接是该功能的文档)。
    • 赞成,因为实际上向 OP 解释了他所看到的,这是他问题的重点。我们可以整天谈论这是如何 UB 并且不能依赖,但最初的问题要求解释 OP 看到的特定行为,顺便说一句,这是许多堆管理器的典型行为。
    • 也就是说:“在运行 free() 之后,您应该认为指针在重新分配之前不可用,因为另一个进程可能会覆盖该内存空间。”这在任何“常规”操作系统上都是错误的。进程通常不共享堆,因此freed 内存不会被跨进程重用(好吧,除非堆管理器将其交还给操作系统,但在这种情况下,您的进程不再可以访问它并访问它的地址给出了段错误)。当给定的用户模式内存块被重用时是在同一个进程的上下文中(尽管可能是跨线程的)。
    【解决方案4】:

    您想知道当您的计算机以...未定义...方式运行时会发生什么吗?应该:

    • a/ 发出错误消息,
    • b/崩溃,
    • c/ 阅读您的想法并按照您的意图行事,或
    • d/ 以未定义的方式行事?

    提示:前三个选项似乎非常定义,不是吗?

    您的代码调用了未定义的行为。即,您的第一个示例中的这两行:

    printf("string = %s,  Address = %u\n", copy, copy);
    printf("origin = %s,  Address = %u\n", origin, origin);
    

    ...在您的第二个示例中,这一行:

    free(origin);
    
    • 您应该使用%p 打印void * (as David pointed out)。使用%u 打印除unsigned int 之外的任何内容是对printf 的谎言,这会导致未定义的行为。
    • 您不应该使用(即尝试打印它曾经指向的字符串)copy 在您使用freed 之后;这也是未定义的行为。
    • 你不应该free 任何不是malloccallocrealloc 返回的东西;再次,未定义的行为。

    请注意,未定义的行为可能会出现巧合,因此freed 对象曾经包含的字符串可能仍然存在;作为程序员,我们不喜欢依赖巧合,但碰巧行为的未定义性质可能与您的代码巧合相对应。在其他情况下,它也可能会巧合(或不会)巧妙地失败灾难性地。因此,我们应该避免未定义的行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-12
      • 2012-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-17
      • 2018-01-18
      • 2015-04-03
      相关资源
      最近更新 更多