在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()
。我认为它会调用其他一些释放内存的函数。我还在做我的研究。
环境在初始进程堆栈上初始化,就在参数 argc
和 argv
到 main()
之上。
通常情况下,环境生命和参数本身就是整个程序生命,常见的实现方式是将环境字符串、环境数组、主函数参数字符串和命令行数组压入栈中,就在之前推送三个变量(历史上 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
指针)
我用下面的代码测试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()
。我认为它会调用其他一些释放内存的函数。我还在做我的研究。
环境在初始进程堆栈上初始化,就在参数 argc
和 argv
到 main()
之上。
通常情况下,环境生命和参数本身就是整个程序生命,常见的实现方式是将环境字符串、环境数组、主函数参数字符串和命令行数组压入栈中,就在之前推送三个变量(历史上 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
指针)