在 PHP7 扩展中创建循环对象时发生内存泄漏

Memory leak when creating cycled object in PHP7 extension

以下是我创建的测试函数(for PHP 7.1)。

PHP_FUNCTION(tsc_test3)
{
    zend_string *cnA;
    zend_class_entry *ceA;

    // $ret = new ClsA();
    cnA = zend_string_init("ClsA", 4, 0);
    ceA = zend_fetch_class(cnA, ZEND_FETCH_CLASS_DEFAULT);
    zend_string_release(cnA);
    object_init_ex(return_value, ceA);

    // $ret->propA = $ret;
    zval objA;
    ZVAL_COPY(&objA, return_value);
    zend_update_property(ceA, return_value, "propA", 5, &objA);
    zval_ptr_dtor(&objA);

    return;
}

正如评论中所建议的那样,它 returns 是 ClsA 的循环对象。 以下是该功能的测试PHP程序。

<?php
class ClsA {
    public $propA = 1;
}

$x = tsc_test3();

echo "DUMP1 ----\n";
var_dump($x);

for ($i = 0; $i < 10; $i++) {
    echo "Memory usage: ". memory_get_usage(). "\n";
    $x = tsc_test3();
}

echo "DUMP2 ----\n";
var_dump($x);

$x->propA = null;

echo "DUMP3 ----\n";
var_dump($x);

这是 PHP 代码的输出。

DUMP1 ----
object(ClsA)#1 (1) {
  ["propA"]=>
  *RECURSION*
}
Memory usage: 351336
Memory usage: 351392
Memory usage: 351448
Memory usage: 351504
Memory usage: 351560
Memory usage: 351616
Memory usage: 351672
Memory usage: 351728
Memory usage: 351784
Memory usage: 351840
DUMP2 ----
object(ClsA)#11 (1) {
  ["propA"]=>
  *RECURSION*
}
DUMP3 ----
object(ClsA)#11 (1) {
  ["propA"]=>
  NULL
}

var_dump()结果看起来不错,但内存使用量不断增加。

当我使用 ZVAL_COPY_VALUE 而不是 ZVAL_COPY 时,内存使用量没有增加,但它在 DUMP3 中产生了一个奇怪的输出。

DUMP3 ----
*RECURSION*

可能是函数 returns 损坏的对象。

有人能告诉我扩展函数有什么问题吗?

Edit1:发布问题后我注意到 memory_get_usage(true) 没有增加。这是我犯的错误吗?

Edit2:以下 PHP 程序(纯 PHP,无扩展)显示内存使用量增加。这是 PHP 错误还是我误解了什么?我正在使用 PHP 7.1.28.

<?php
class ClsA {
    public $propA = 1;
}

for ($i = 0; $i < 10; $i++) {
    echo "Memory usage: ". memory_get_usage(). "\n";
    $x = new ClsA();
    $x->propA = $x;
}

这是因为 PHP 垃圾收集器并不总是回收您的对象(ClsA 实例)在 确切 将引用 ($x) 分配给另一个对象的那一刻。 PHP 完全使用引用计数和垃圾收集。

如果您在每个循环中强制执行垃圾回收,您会发现您的内存占用量将保持不变:

<?php
class ClsA {
    public $propA = 1;
}

for ($i = 0; $i < 10; $i++) {
    gc_collect_cycles();
    echo "Memory usage: ". memory_get_usage(). "\n";
    $x = new ClsA();
    $x->propA = $x;
}

输出:

$ php test.php
Memory usage: 396296 <-- before the first ClsA allocation
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384
Memory usage: 396384

更多(技术)信息在这里:https://www.php.net/manual/en/features.gc.collecting-cycles.php