在C中,为什么getenv返回的指针会被自动回收?

In C, why is the pointer returned by getenv automatically reclaimed?

我用下面的代码测试getenv返回的指针,如果不free,测试会导致内存泄漏

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

void demo() {
    const char *home = getenv("HOME");
    printf("%s\n", home);
}

int main() {
    demo();
    return 0;
}

我使用 Valgrind 检测内存泄漏:

$ gcc main.c -o main
$ valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./main

结果如下:

==134679== Memcheck, a memory error detector
==134679== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==134679== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==134679== Command: ./demo
==134679== 
/home/aszswaz
==134679== 
==134679== HEAP SUMMARY:
==134679==     in use at exit: 0 bytes in 0 blocks
==134679==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==134679== 
==134679== All heap blocks were freed -- no leaks are possible
==134679== 
==134679== For lists of detected and suppressed errors, rerun with: -s
==134679== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

说明申请了一块内存,回收了一块内存。为什么用getenv得到的指针可以自动回收?

如果我在代码中添加free((void *)home),会不会影响全局环境变量?

https://man7.org/linux/man-pages/man3/getenv.3.html 没有提到 getenv() 的 return 值分配在堆上。因此,可以把它想象成 argv.

虽然这种情况很少发生,我自己也从未见过,但在某些实现中,getenv() 可能会在堆上分配内存并 return 指向它的指针。因此,最好查看操作系统的手册页。

只有在堆上分配的内存必须是free()d.

并且,指向堆上分配的内存的指针 通常 仅在您调用 malloc()calloc()realloc()strdup()、等等

总之:

如果您在由 getenv() 编辑的 return 指针上调用 free(),如果您的操作系统没有 return 指向内存的指针,则这是未定义的行为堆。

哇,我差点没看到!:
我认为这就是让您感到困惑的地方:

usage: 1 allocs, 1 frees

printf() 可以在堆上分配并在 return 返回之前释放它。

编辑:

使用gdb,我们可以发现:

#0  __GI___libc_malloc (1024) // and indeed it calls with 1024 bytes
#1  __GI__IO_file_doallocate ()
#2  __GI__IO_doallocbuf ()
#4  _IO_new_file_xsputn ()
#5  _IO_new_file_xsputn ()
#6  __GI__IO_puts
#7  demo ()
#8  main ()

printf()好像被puts()代替了,puts()通过很长的函数调用链调用malloc()

但是,它好像没有调用free()。我认为它会调用其他一些释放内存的函数。我还在做我的研究。

环境在初始进程堆栈上初始化,就在参数 argcargvmain() 之上。

通常情况下,环境生命和参数本身就是整个程序生命,常见的实现方式是将环境字符串、环境数组、主函数参数字符串和命令行数组压入栈中,就在之前推送三个变量(历史上 main() 有第三个参数 environ,也传递给 main)出于遗留代码原因,此环境指针仍传递给 main.

只需尝试以下程序:

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

int main(int argc, char **argv, char **environ)
{
    printf("argc = %d\n", argc);
    printf("args:");
    for (int i = 0; i < argc; i++) {
        printf(" [%s]", argv[i]);
    }
    printf("\n\nEnvironment:\n");
    for (char **p = environ; *p; p++) {
        printf("  %s\n", *p);
    }
    return 0;
}

what getenv() returns 不是静态分配的数据,也不是堆中动态分配的数据。它存储在 main() 参数上方的堆栈中(几乎所有 POSIX-like 操作系统都以相同的方式解决此问题)(您可以使用调试器检查它,以查看字符串的确切位置,我也已经做了)

所以您的问题与 getenv() 无关,getenv() 不使用 malloc() 到 return 动态分配给您的字符串。 您需要在别处寻找 Valgrind 识别为动态的内存(它没有显示任何问题)

认为 valgrind 识别了您使用 malloc()(或其任何朋友)而不是 free()d 分配的内存,并且报告说在 exit() 处没有分配内存.所以 getenv() 不会分配内存来给你环境内容。

如果您阅读内核如何初始化进程内存的初始堆栈,您会发现非常有趣的事情(从堆栈的最深处到顶部列出):

  • 有一些固定的机器代码可以在有挂起的中断时从内核模式正确地return(只有当内核从内核模式切换到用户模式时才会执行中断,因为它们必须在用户模式下执行处理程序代码模式,并且从不在内核模式下,原因很明显)
  • 有一个遗留结构来保存命令行参数并允许内核访问命令参数以使 ps(1) 命令正常工作。这不再是真的,因为它代表了将内核信息放入用户 space 中的安全漏洞,但该结构仍然存在,供遗留代码使用。命令的参数现在在内核中 space,并且只能通过特殊的系统调用进行修改。
  • 所有与进程的接收环境相关联的字符串。
  • 指向环境字符串的指针数组也存储在堆栈中。这包括一个最终的 NULL 指针,以便能够识别数组的末尾。
  • 命令行参数的所有字符串也存储在那里。
  • 命令行参数的指针数组(包括最后一个NULL指针,不计入argc
  • 参数 environ 带有指向环境字符串数组的指针。
  • 带有指向命令行参数数组的指针的参数argv
  • 带命令行参数个数的参数argc(这包括程序名,但不包括最后一个NULL指针)