PHP扩展开发之最详细的RETURN_STRINGL讲解
RETURN_STRINGL
的定义如下:
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
第一个参数s
是我们要传入的字符串地址——( char * )。第二个参数l
是字符串的长度。 第三个参数duplicate
: 这个参数表示的是新申请一个空间保存传入的字符串,还是直接使用传入的字符串。
例如:
PHP_FUNCTION(varinfo)
{
char *var_name = NULL;
int var_name_len,len;
char *p;
zval **var;
int res;
HashTable *vars;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&var_name,&var_name_len) == FAILURE) {
return ;
}
vars = EG(active_symbol_table);
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
return;
}else{
PHPWRITE("no such symbol.\n",16);
}
RETURN_STRINGL(p,len,0);
}
在这个例子中,p
是通过内存分配函数realloc
直接向操作系统申请的内存。如果直接作为返回值,函数是可以将字符串返回的,但是这里会有一个问题就是在释放内存的过程中会出现错误。
Zend/zend_execute.h(95):Block 0x7fb984802800 status:
.../php56/Zend/zend_variables.c(37):Actuallocation(locationwasrelayed)
Invalid pointer:((thread_id=0x834A7CA0)!=(expected=0x118F6DC0))
Invalid pointer:((size=0x00000000)!=(next.prev=0x7fb983723c10))
为什么会出现这种问题呢,原因就是php有自己的内存管理。因为我们的p
是直接向操作系统申请的内存,而不是通过PHP的内存管理分配的内存,所以在最后释放内存的时候发现这块儿内存不是有效的内存,因此会报错。 通过上面的错误信息我们也大概能看出原因。
所以这就需要RETURN_STRINGL
的第三个参数了, 将第三个参数传1,让系统先通过PHP内存管理分配一块儿内存,然后将p
地址的内容复制到新分配的内存中。这样在释放的时候就不会有问题了。
PHP_FUNCTION(varinfo)
{
...
RETURN_STRINGL(p,len,1);
当然,除了这种方式,我们也可以自己手动使用PHP的内存管理分配一个新的内存。代码如下:
PHP_FUNCTION(varinfo)
{
char *var_name = NULL;
int var_name_len,len;
char *p,*strg;
zval **var;
int res;
HashTable *vars;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&var_name,&var_name_len) == FAILURE) {
return ;
}
vars = EG(active_symbol_table);
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
}else{
PHPWRITE("no such symbol.\n",16);
return;
}
len = spprintf(&strg, 0, "%s", p);
RETURN_STRINGL(strg,len,0);
}
spprintf
函数会自动分配给变量strg
内存。这种方式也是可以的。
通过RETURN_STRINGL
可以说明一类问题。像返回整型、布尔型等等,原理都是一样的。要注意内存的释放,处理不好容易导致内存泄漏
扩展1 —— 内存泄漏简单说明
关于内存泄漏,PHP底层会为我们进行检测。前提是我们的变量都是通过PHP的内存管理进行分配的。如果是我们自己手动直接向操作系统申请的,PHP底层是不容易检测的,需要借助其他工具来检测。我们这里先简单介绍PHP本身的检测。
内存泄漏这很容易理解。就是我们通过PHP内存管理申请的内存之后,最终没有进行释放。还是拿上面的例子,我们稍微改动一下
PHP_FUNCTION(varinfo)
{
char *var_name = NULL;
int var_name_len,len;
char *p,*strg;
zval **var;
int res;
HashTable *vars;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&var_name,&var_name_len) == FAILURE) {
return ;
}
vars = EG(active_symbol_table);
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
}else{
PHPWRITE("no such symbol.\n",16);
return;
}
len = spprintf(&strg, 0, "%s", p);
RETURN_STRINGL(p,len,1);
}
这里,我们使用PHP内存管理器给变量strg
分配了内存,但是最后我们并没有将其作为return_value
返回给应用层,这就导致PHP底层回收的时候不能正确回收变量strg
的内存,所以这里会检测到有可能会造成内存泄漏
.../php56/main/spprintf.c(794) : Freeing 0x10252CC68 (79 bytes), script=/Users/mihuan/workspace/php/varinfo.php
=== Total 1 memory leaks detected ===
那为什么如果将strg
传给return_value
作为返回值就不会造成内存泄漏呢。因为PHP底层会对return_value
这种默认的变量自动进行回收。从而对于strg
的内存也会进行回收。
扩展2 —— valgrind 内存泄漏检测
上面我们提过,对于不是使用PHP内存管理器分配的内存,PHP底层是不容易检测是否存在内存泄漏的可能。例如下面的例子
PHP_FUNCTION(varinfo)
{
char *var_name = NULL;
int var_name_len,len;
char *p,*strg;
zval **var;
int res;
HashTable *vars;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&var_name,&var_name_len) == FAILURE) {
return ;
}
vars = EG(active_symbol_table);
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
}else{
PHPWRITE("no such symbol.\n",16);
return;
}
len = spprintf(&strg, 0, "%s", p);
RETURN_STRINGL(strg,len,0);
}
变量p
并没有手动进行释放,并且PHP底层也不会自动对其进行回收,因为这是直接向操作系统申请的内存。 但是在执行的过程中PHP底层并不会报出内存泄漏的错误。这时我们就要借助第三方工具——valgrind
进行检测
$ valgrind --leak-check=full --show-reachable=yes /usr/local/bin/php -f /root/workspace/php/varinfo.php
得出的结果如下
...
==27216==1,025bytesin1blocksaredefinitelylostinlossrecord11of11
==27216==at 0x4C29DAD:malloc(vg_replace_malloc.c:308)
==27216==by 0x4C2C100:realloc(vg_replace_malloc.c:836)
==27216==by 0xED4AD57:???
==27216==by 0xED4AE67:???
==27216==by 0xED4B092:???
==27216==by 0x8FA213:zend_do_fcall_common_helper_SPEC(zend_vm_execute.h:558)
==27216==by 0x900D32:ZEND_DO_FCALL_SPEC_CONST_HANDLER(zend_vm_execute.h:2602)
==27216==by 0x8F946C:execute_ex(zend_vm_execute.h:363)
==27216==by 0x8F9552:zend_execute(zend_vm_execute.h:388)
==27216==by 0x8AD44D:zend_execute_scripts(zend.c:1341)
==27216==by 0x7EEA4B:php_execute_script(main.c:2613)
==27216==by 0x976345:do_cli(php_cli.c:998)
==27216==
==27216==LEAK SUMMARY:
==27216==definitely lost:1,025bytesin1blocks
==27216==indirectly lost:0bytesin0blocks
==27216==possibly lost:0bytesin0blocks
==27216==still reachable:552bytesin10blocks
==27216==suppressed:0bytesin0blocks
==27216==
==27216==Forlistsofdetectedandsuppressederrors,rerun with:-s
==27216==ERROR SUMMARY:1errorsfrom1contexts(suppressed:0from0)
很明显上面报出了一个错误。这就是变量p
没有被手动释放造成的。下面我们来手动释放
PHP_FUNCTION(varinfo)
{
...
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
}else{
PHPWRITE("no such symbol.\n",16);
return;
}
len = spprintf(&strg, 0, "%s", p);
free(p); // 释放内存
RETURN_STRINGL(strg,len,0);
}
然后再次运行上面的valgrind
命令,得到结果如下
==27864==176bytesin1blocksarestillreachableinlossrecord10of10
==27864==at 0x4C29E63:malloc(vg_replace_malloc.c:309)
==27864==by 0x640F5B7:CRYPTO_malloc(in/usr/lib64/libcrypto.so.1.0.2k)
==27864==by 0x64C747F:lh_new(in/usr/lib64/libcrypto.so.1.0.2k)
==27864==by 0x6410984:???(in/usr/lib64/libcrypto.so.1.0.2k)
==27864==by 0x6410A34:???(in/usr/lib64/libcrypto.so.1.0.2k)
==27864==by 0x6410B83:???(in/usr/lib64/libcrypto.so.1.0.2k)
==27864==by 0x47BAA5:zm_startup_openssl(openssl.c:1153)
==27864==by 0x8B56D9:zend_startup_module_ex(zend_API.c:1797)
==27864==by 0x8B5786:zend_startup_module_int(zend_API.c:1810)
==27864==by 0x8C12FD:zend_hash_apply(zend_hash.c:641)
==27864==by 0x8B5D65:zend_startup_modules(zend_API.c:1930)
==27864==by 0x7EDE93:php_module_startup(main.c:2323)
==27864==
==27864==LEAK SUMMARY:
==27864==definitely lost:0bytesin0blocks
==27864==indirectly lost:0bytesin0blocks
==27864==possibly lost:0bytesin0blocks
==27864==still reachable:552bytesin10blocks
==27864==suppressed:0bytesin0blocks
==27864==
==27864==Forlistsofdetectedandsuppressederrors,rerun with:-s
==27864==ERROR SUMMARY:0errorsfrom0contexts(suppressed:0from0)
错误已经消失了。
上面举的例子有些变量其实是多余的,我们这里仅仅是为了说明情况。例如,去掉变量strg
PHP_FUNCTION(varinfo)
{
char *var_name = NULL;
int var_name_len,len;
char *p;
zval **var;
int res;
HashTable *vars;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s",&var_name,&var_name_len) == FAILURE) {
return ;
}
vars = EG(active_symbol_table);
if(hash_find(&var,vars,var_name,var_name_len TSRMLS_CC)) {
php_printf("%s: ", var_name);
p = get_var_info(var,&len TSRMLS_CC);
}else{
PHPWRITE("no such symbol.\n",16);
return;
}
RETURN_STRINGL(p,len,1);
}
但是这个方式就又有了问题了,p
没有地方手动进行释放。所以还需要替换RETURN_STRINGL
PHP_FUNCTION(varinfo)
{
...
// RETURN_STRINGL(p,len,1);
// 替换如下
RETVAL_STRINGL(p,len,1);
free(p);
return;
}
申请的内存一定要记着释放。在开发PHP扩展过程中,一定要区分是使用何种方式申请的内存,然后使用相应的方法进行释放。
本文转载自:迹忆客(https://www.jiyik.com)
以上是 PHP扩展开发之最详细的RETURN_STRINGL讲解 的全部内容, 来源链接: utcz.com/z/290183.html