內容均以php5.6.14為例.
一. 封裝函數時產生 memory leaks.
[weichen@localhost www]$ php 2.php [122,3333] [Tue Jul 10 15:34:42 2016] Script: '/home/www/2.php' /home/weichen/Downloads/pdoner/pdoner.c(83) : Freeing 0x7F86B52F79F8 (32 bytes), script=/home/www/2.php [Tue Jul 10 15:34:42 2016] Script: '/home/www/2.php' /home/weichen/Downloads/php-5.6.14/ext/standard/string.c(1161) : Freeing 0x7F86B52F7B60 (79 bytes), script=/home/www/2.php === Total 2 memory leaks detected ===
php編譯開啟 --enable-debug,如果擴展中存在內存泄漏,會有相應提示。內存泄漏問題相當困擾。
為什么會有內存泄露?是你的函數一直在申請內存做某件事,而功能完成后沒有釋放內存。
網上的 hello world 程序很多,基本是不講內存處理的,即便稍作修改,也無法用於真實項目。
所以釋放內存也是底層程序的關鍵點。現在分析一下上面的提示信息,總共檢測有 2 處內存泄漏。
第(1)處:
[Tue Jul 10 15:34:42 2016] Script: '/home/www/2.php' /home/weichen/Downloads/pdoner/pdoner.c(83) : Freeing 0x7F86B52F79F8 (32 bytes), script=/home/www/2.php
提示我們 pdoner.c(83) 有問題,回到程序中是 MAKE_STD_ZVAL(glue); 給 glue 初始化沒問題,問題是用完了沒有釋放,很容易想到的是要釋放掉 glue。
zval_ptr_dtor(&glue);
編譯安裝依舊有問題,出現段錯誤一般是指針使用有誤:
[weichen@localhost www]$ php 2.php [Tue Jul 10 14:58:25 2016] Script: '/home/www/2.php' --------------------------------------- /home/weichen/Downloads/php-5.6.14/Zend/zend_execute.h(79) : Block 0x7fb3f93459c8 status: /home/weichen/Downloads/php-5.6.14/Zend/zend_variables.c(37) : Actual location (location was relayed) Invalid pointer: ((thread_id=0x007A7C7A) != (expected=0x06567840)) Segmentation fault (core dumped)
zval_ptr_dtor 使用有什么講究?這時候最好先查閱一下內核中的用法。
zend_API.h 中有這樣一處用法:
#define ZVAL_ZVAL(z, zv, copy, dtor) do { \ zval *__z = (z); \ zval *__zv = (zv); \ ZVAL_COPY_VALUE(__z, __zv); \ if (copy) { \ zval_copy_ctor(__z); \ } \ if (dtor) { \ if (!copy) { \ ZVAL_NULL(__zv); \ } \ zval_ptr_dtor(&__zv); \ } \ } while (0)
注意,在調用 zval_ptr_dtor 銷毀 __zv 之前,調用了 ZVAL_NULL(__zv) 把指針置為null。
所以我們照這種方式,把 glue 設為null。
ZVAL_NULL(glue);
zval_ptr_dtor(&glue);
編譯運行,可以看到只剩一處提示了。
第(2)處:
[Tue Jul 10 15:34:42 2016] Script: '/home/www/2.php' /home/weichen/Downloads/php-5.6.14/ext/standard/string.c(1161) : Freeing 0x7F86B52F7B60 (79 bytes), script=/home/www/2.php
當時這個還沒有解決掉,先繼續往下看,回頭再一起看這個的解決辦法。
二. 封裝類時產生 memory leaks.
PDONER_ERRS_* 均為定義的常量,下面是無內存泄漏的版本:
/* {{{ proto public Errs::__construct(void) */ PHP_METHOD(errs, __construct) { zval *msg; MAKE_STD_ZVAL(msg); array_init(msg); add_index_string(msg, PDONER_ERRS_SUCC, "成功", 1); add_index_string(msg, PDONER_ERRS_FAIL, "失敗", 1); add_index_string(msg, PDONER_ERRS_EXCEP, "異常", 1); add_index_string(msg, PDONER_ERRS_UNKNOW, "未知", 1); zend_update_property(errs_ce, getThis(), ZEND_STRL(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC); zval_ptr_dtor(&msg); /* add_index_string(msg, PDONER_ERRS_SUCC, "成功", 0); add_index_string(msg, PDONER_ERRS_FAIL, "失敗", 0); add_index_string(msg, PDONER_ERRS_EXCEP, "異常", 0); add_index_string(msg, PDONER_ERRS_UNKNOW, "未知", 0); add_property_zval_ex(getThis(), PDONER_ERRS_PROPERTY_NAME_MSG, sizeof(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC); */ } /* }}} */
擴展類的屬性無法直接初始化成數組和對象,所以只能以修改屬性的方式操作,在構造函數內 或者 MINIT 階段。
注意,由於屬性賦值在 construct 階段,如果沒有實例化類,擴展內部 zend_read_property 時還是沒有值的。
zend_update_property 第二個參數是當前對象,所以沒有辦法用在 MINIT 階段,上面用法參考了 Yaf-2.3.5:
PHP_METHOD(yaf_config_ini, __construct) { zval *filename, *section = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &filename, §ion) == FAILURE) { zval *prop; MAKE_STD_ZVAL(prop); array_init(prop); zend_update_property(yaf_config_ini_ce, getThis(), ZEND_STRL(YAF_CONFIG_PROPERT_NAME), prop TSRMLS_CC); zval_ptr_dtor(&prop); return; } (void)yaf_config_ini_instance(getThis(), filename, section TSRMLS_CC); }
非理想情況下:
1). 我們的構造函數中,如果傳了非 duplicate 的參數:add_index_string(msg, PDONER_ERRS_SUCC, "成功", 0);
調用 Errs::$msg 可以成功輸出內容,但是有段錯誤:
[Thu Jul 10 17:04:38 2016] Script: '/home/www/2.php' --------------------------------------- /home/weichen/Downloads/php-5.6.14/Zend/zend_execute.h(79) : Block 0x7fbf5ed4bb4e status: /home/weichen/Downloads/php-5.6.14/Zend/zend_variables.c(37) : Actual location (location was relayed) Invalid pointer: ((thread_id=0x646F6C70) != (expected=0x6BF6E840)) Segmentation fault (core dumped)
Reference:when to duplicate string using add_index_string?
http://grokbase.com/t/php/php-dev/01285y74sp/when-to-duplicate-string-using-add-index-string
有其它地方用,則復制一份出去;沒有其它地方用,duplicate 填 0 .
2). 啟用注釋段代碼的情況,可以成功輸出,卻有 11 處內存泄漏:
[Thu Jul 10 17:06:56 2016] Script: '/home/www/2.php' /home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1369) : Freeing 0x7F1900D44820 (72 bytes), script=/home/www/2.php /home/weichen/Downloads/php-5.6.14/Zend/zend_hash.c(419) : Actual location (location was relayed) Last leak repeated 3 times [Thu Jul 10 17:06:56 2016] Script: '/home/www/2.php' /home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1366) : Freeing 0x7F1900D448C8 (32 bytes), script=/home/www/2.php Last leak repeated 3 times [Thu Jul 10 17:06:56 2016] Script: '/home/www/2.php' /home/weichen/Downloads/pdoner/pdoner.c(120) : Freeing 0x7F1900D45C58 (32 bytes), script=/home/www/2.php [Thu Jul 10 17:06:56 2016] Script: '/home/www/2.php' /home/weichen/Downloads/php-5.6.14/Zend/zend_hash.c(392) : Freeing 0x7F1900D45D58 (64 bytes), script=/home/www/2.php /home/weichen/Downloads/php-5.6.14/Zend/zend_alloc.c(2583) : Actual location (location was relayed) [Thu Jul 10 17:06:56 2016] Script: '/home/www/2.php' /home/weichen/Downloads/pdoner/pdoner.c(121) : Freeing 0x7F1900D467C0 (72 bytes), script=/home/www/2.php /home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1011) : Actual location (location was relayed) === Total 11 memory leaks detected ===
可見是沒有銷毀 zval *msg 的緣故。你加上 zval_ptr_dtor(&msg); 又會報段錯誤。
注意上面的用法沒有這樣用:add_property_zval_ex(getThis(), ZEND_STRL(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC);
三. 字符串返回值的難題.
回到第(2)處的字符串問題上,剛開始是這么做的:
char *src1 = "["; char *ori = Z_STRVAL_P(return_value); char *src2 = "]"; char *dest = (char *)emalloc(1024); strcat(dest, src1); strcat(dest, ori); strcat(dest, src2); RETURN_STRING(dest, 0);
顯然 dest 是長期占用內存的,但你如何在返回值之后,還能再把它銷毀呢,恐怕無法做到。
這里就要引入一個概念,當你的函數沒有返回值時,函數默認返回的變量是 zval *return_value,也就是你用它就不會有問題。
另外,我們用內核中提供的字符串連接函數 concat_function(zval *result, zval *op1, zval *op2) 代替 strcat 更有效的處理。
concat_function 在 ./Zend/zend_operators.c:1422,有時候不清楚用法最好是看它的實現。
使用 concat_function 之后,有一些要注意的問題,看代碼:
// 第一種方法,引入一個變量 zval result; concat_function(&result, &op1, return_value TSRMLS_CC); concat_function(&result, &result, &op2 TSRMLS_CC); // 1. zval_ptr_dtor(&return_value) was wrong. // 2. forget zval_dtor(return_value) will cause memory leaks. zval_dtor(return_value); // copy result to return_value; // if "zval result" is not zero-terminated, use ZVAL_ZVAL() instead, like the way 2. (PHP Warning: String is not zero-terminated.) ZVAL_COPY_VALUE(return_value, &result); // 第二種方法,更簡潔 concat_function(&op1, &op1, return_value TSRMLS_CC); concat_function(&op1, &op1, &op2 TSRMLS_CC); zval_dtor(return_value); ZVAL_ZVAL(return_value, &op1, 0, 1); zval_dtor(&op1); zval_dtor(&op2);
上面是 pdoner 擴展函數 pd_implode_json 的實現:https://github.com/farwish/pdoner
php 未開啟 debug 模式情況下使用 valgrind 工具:http://tina.reeze.cn/book/?p=chapt06/06-07-memory-leaks
Link: http://www.cnblogs.com/farwish/p/5663993.html