在C语言中,为什么结构的第一个成员经常;重置";当它与free()解除分配时,是否为0? 设置

在C语言中,为什么结构的第一个成员经常;重置";当它与free()解除分配时,是否为0? 设置,c,memory-management,free,C,Memory Management,Free,假设我有一个struct父对象,其中包含成员变量,例如int,还有另一个struct(因此父对象是嵌套的结构)。这是一个示例代码: struct mystruct { int n; }; struct father { int test; struct mystruct M; struct mystruct N; }; 在main函数中,我们使用malloc()分配内存,以创建类型为struct-father的新结构,然后填充其成员变量及其子变量: s

假设我有一个
struct父对象
,其中包含成员变量,例如
int
,还有另一个
struct
(因此
父对象
是嵌套的
结构
)。这是一个示例代码:

struct mystruct {
    int n;
};

struct father {
    int test;
    struct mystruct M;
    struct mystruct N;
};
在main函数中,我们使用
malloc()
分配内存,以创建类型为
struct-father
的新结构,然后填充其成员变量及其子变量:

    struct father* F = (struct father*) malloc(sizeof(struct father));
    F->test = 42;
    F->M.n = 23;
    F->N.n = 11;
然后,我们从
struct
s外部获取指向这些成员变量的指针:

    int* p = &F->M.n;
    int* q = &F->N.n;
之后,我们打印执行
free(F)
前后的值,然后退出:

    printf("test: %d, M.n: %d, N.n: %d\n", F->test, *p, *q);
    free(F);
    printf("test: %d, M.n: %d, N.n: %d\n", F->test, *p, *q);
    return 0;
这是一个示例输出(*):

*:使用gcc(ubuntu9.3.0-17ubuntu1~20.04)9.3.0

pastebin上的完整代码:

问题 这就是我用来测试如何使用free()释放内存的测试程序。我的想法(通过阅读K&R“8.7示例-存储分配器”,其中实现并解释了
free()
的一个版本)是,当您
free()
结构时,您几乎只是告诉操作系统或程序的其他部分,您将不会使用以前分配给
malloc()
的内存中的特定空间。所以,释放这些内存块后,成员变量中应该有垃圾值,对吗?我可以看到,在测试程序中,
N.N
会发生这种情况,但是,随着我运行越来越多的样本,很明显,在绝大多数情况下,这些成员变量比任何其他“随机”值都“重置”为0。我的问题是:为什么会这样?这是因为堆栈/堆比任何其他值更频繁地填充零吗


最后请注意,这里有一些相关问题的链接,但这些链接无法回答我的特定问题:


当动态分配的对象被释放时,它不再存在。任何后续访问它的尝试都具有未定义的行为。因此,问题是毫无意义的:已分配的
结构的成员在宿主结构的生命周期结束时不再存在,因此无法在该点设置或重置为任何内容。没有有效的方法尝试为这些不再存在的对象确定任何值。

调用
free
后,指针
F
p
q
不再指向有效内存。尝试取消引用这些指针将调用。事实上,在调用
free
之后,这些指针的值变得不确定,因此您也可以通过读取这些指针值来调用UB

因为取消引用这些指针是未定义的行为,编译器可以假设它永远不会发生,并基于该假设进行优化


也就是说,没有任何东西表明
malloc
/
free
实现必须保持存储在已释放内存中的值不变或将其设置为特定值。它可能会将其内部簿记状态的一部分写入刚刚释放的内存,也可能不会。您必须查看glibc的源代码,才能确切了解它在做什么。

调用
free
时会发生两件事:

  • 在C计算模型中,任何指向已释放内存的指针值(无论是它的开始,如
    F
    ,还是它内部的东西,如
    p
    q
    )都不再有效。C标准没有定义当您尝试使用这些指针值时会发生什么,如果您尝试使用它们,编译器的优化可能会对程序的行为产生意外的影响
  • 释放的内存用于其他目的。它最常见的其他用途之一是跟踪可分配的内存。换句话说,实现
    malloc
    free
    的软件需要数据结构来记录已释放的内存块和其他信息。当您
    释放
    内存时,该软件通常会为此使用部分内存。这可能会导致您看到的变化

释放的内存也可能被程序中的其他东西使用。在没有信号处理程序或类似程序的单线程程序中,通常没有软件会在
空闲
和准备所显示的
printf
参数之间运行,因此,没有其他任何东西能够如此快速地重用内存,
malloc
软件的重用是您观察到的最有可能的解释。但是,在多线程程序中,另一个线程可能会立即重用内存。(实际上,这可能有点不太可能,因为
malloc
软件可能会优先为单独的线程保留单独的内存池,以减少必要的线程间同步量。)

该标准对释放后使用指向已分配存储的指针的程序的行为没有任何规定。实现可以通过指定超过标准要求的更多程序的行为来自由扩展语言,标准的作者旨在鼓励实现的多样性,从而在市场指导的实现质量基础上支持流行的扩展。一些指向死对象的指针操作得到广泛支持(例如,给定
char*x,*y;
如果程序执行
free(x),则标准允许一致性实现以任意方式运行;y=x;
x
为非空的情况下,不考虑任何东西在初始化后是否使用
y
执行任何操作,但大多数实现都会扩展语言以保证此类代码不会产生任何效果
test: 42, M.n: 23, N.n: 11
test: 0, M.n: 0, N.n: 1025191952
int test(char *p1, char *p2)
{
  char *q;
  if (*p1)
  {
    q = malloc(0):
    free(q);
    return *p1+*p2;
  }
  else
    return 0;
}