Java“ Foo f = new Foo()”中的对象初始化与使用malloc作为C语言中的指针是否基本相同?

Jul*_*les 9 c java memory memory-management object

我试图了解Java中对象创建背后的实际过程-并且我想使用其他编程语言。

假定Java中的对象初始化与对C中的结构使用malloc相同是错误的吗?

例:

Foo f = new Foo(10);
Run Code Online (Sandbox Code Playgroud)
typedef struct foo Foo;
Foo *f = malloc(sizeof(Foo));
Run Code Online (Sandbox Code Playgroud)

这就是为什么说对象位于堆而不是堆栈上的原因吗?因为它们本质上只是数据指针?

par*_*par 5

在C语言中,malloc()在堆中分配一个内存区域并返回一个指向它的指针。那就是你所得到的。内存是未初始化的,您不能保证它全为零或其他任何值。

在Java中,调用new就像一样进行基于堆的分配malloc(),但是您还会获得很多额外的便利(如果愿意,也可以增加开销)。例如,您不必显式指定要分配的字节数。编译器会根据您要分配的对象类型为您解决问题。此外,还调用了对象构造函数(如果您想控制初始化的发生方式,可以将其传递参数)。当new返回时,你保证有一个已初始化的对象。

但是,是的,在调用结束时,malloc()和的结果new都只是指向某些基于堆的数据块的指针。

问题的第二部分询问堆栈和堆之间的区别。通过学习(或阅读有关)编译器设计课程,可以找到更全面的答案。操作系统课程也将有所帮助。关于堆栈和堆,SO上也有许多问题和答案。

话虽如此,我将给出一个总体概述,我希望它不要太冗长,并且旨在从较高的层次上解释这些差异。

从根本上讲,拥有两个内存管理系统(即堆和堆栈)的主要原因是为了提高效率。第二个原因是,在某些类型的问题上,每种方法都比另一种方法更好。

作为一个概念,堆栈对于我来说比较容易理解,因此我从堆栈开始。让我们考虑一下C中的此功能...

int add(int lhs, int rhs) {
    int result = lhs + rhs;
    return result;
}
Run Code Online (Sandbox Code Playgroud)

以上似乎很简单。我们定义一个名为的函数,add()并传入左右加数。该函数将它们相加并返回结果。请忽略所有可能发生的情况,例如溢出,这与讨论无关。

add()函数的目的似乎很简单,但是我们可以说出它的生命周期吗?特别是它的内存利用率需要吗?

最重要的是,编译器先验地(即在编译时)知道数据类型有多大以及将使用多少种。的lhsrhs参数是sizeof(int),4个字节的每一个的32位的系统上,8个字节的每一个的64位系统。变量result也是sizeof(int)。假设我们使用的是64位系统。编译器可以告诉该add()函数使用8 bytes * 3 ints或总共24个字节的内存。

add()调用该函数时,称为堆栈指针的硬件寄存器将具有指向堆栈顶部的地址。为了分配add()功能需要运行的内存,所有功能输入代码需要做的是发出一条单一的汇编语言指令,以将堆栈指针寄存器的值减24。这样做,它将在堆栈上创建三个存储空间。ints,每一个用于lhsrhs,和result。通过执行一条指令来获取所需的存储空间在速度方面是一个巨大的胜利,因为一条指令往往在一个时钟周期内执行(1 GHz CPU每秒执行十亿分之一秒)。

而且,从编译器的角度来看,它可以创建一个指向变量的映射,这些变量看起来很像索引数组:

lhs:     ((int *)stack_pointer_register)[0]
rhs:     ((int *)stack_pointer_register)[1]
result:  ((int *)stack_pointer_register)[2]
Run Code Online (Sandbox Code Playgroud)

同样,所有这些都非常快。

add()函数退出时必须进行清理。它通过从堆栈指针寄存器中减去24个字节来实现。这类似于对的调用,free()但是它仅使用一条CPU指令,并且只需要花费一勾。非常非常快。


现在考虑基于堆的分配。当我们不知道先验需要多少内存时(即我们只会在运行时了解它),这就会起作用。

考虑以下功能:

int addRandom(int count) {
    int numberOfBytesToAllocate = sizeof(int) * count;
    int *array = malloc(numberOfBytesToAllocate);
    int result = 0;

    if array != NULL {
        for (i = 0; i < count; ++i) {
            array[i] = (int) random();
            result += array[i];
        }

        free(array);
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

请注意,该addRandom()函数在编译时不知道count参数的值。因此,array像我们将其放入堆栈那样尝试定义是没有意义的,如下所示:

int array[count];

如果count太大,可能会导致我们的堆栈变得太大,并覆盖其他程序段。当此堆栈溢出发生时,您的程序将崩溃(或更糟)。

因此,在直到运行时都不知道需要多少内存的情况下,我们使用malloc()。然后,我们可以只在需要时问我们需要的字节数,malloc()然后检查它是否可以出售那么多字节。如果可以的话,很好,我们将其取回,否则,我们将得到一个NULL指针,该指针告诉我们对malloc()失败的调用。值得注意的是,该程序不会崩溃!当然,作为资源程序员,您可以决定如果资源分配失败,则不允许您的程序运行,但是程序员发起的终止与虚假崩溃不同。

所以现在我们必须回头看看效率。堆栈分配器非常快-一条分配指令,一条释放指令,这是由编译器完成的,但是请记住,堆栈是用于已知大小的局部变量之类的,因此它往往很小。

另一方面,堆分配器要慢几个数量级。它必须在表中进行查找,以查看其是否有足够的可用内存来出售用户所需的内存量。它有它出售该内存,以使后更新这些表肯定没有其他人可以使用块(这簿记可能需要分配到备用内存本身除了它计划鬻)。分配器必须采用锁定策略,以确保它以线程安全的方式出售内存。当记忆终于来了free()d,它发生在不同的时间,并且通常没有可预测的顺序,分配器必须找到连续的块并将它们缝合在一起以修复堆碎片。如果听起来这将需要一个以上的CPU指令来完成所有这些操作,那么您是对的!这非常复杂,需要一段时间。

但是堆很大。比堆栈大得多。我们可以从它们那里获得很多内存,当我们在编译时不知道需要多少内存时,它们就很棒。因此,我们要权衡一个托管内存系统的速度,该内存系统会礼让我们,而不是在尝试分配太大的内存时崩溃。

我希望这有助于回答您的一些问题。如果您想对上述任何一项进行澄清,请告诉我。