1

In a PHP extension, what is the difference between this:

PHP_METHOD(TestExtension, test)
{
  MyClass *myclass;
  MyClass_Object *obj = (MyClass_Object*)zend_object_store_get_object(getThis() TSRMLS_CC);
  myclass = obj->myclass;
  if (myclass != NULL) 
  {
    string retval = myclass->test();
    RETURN_STRING(retval.c_str(), 1);
  }
  RETURN_NULL();
}

and this:

PHP_METHOD(TestExtension, test)
{
  MyClass *myclass;
  MyClass_Object *obj = (MyClass_Object*)zend_object_store_get_object(getThis() TSRMLS_CC);
  myclass = obj->myclass;
  if (myclass != NULL) 
  {
    RETURN_STRING(myclass->test().c_str(), 1);
  }
  RETURN_NULL();
}

?

Both seem to work, but when I run valgrind with:

valgrind --tool=memcheck --num-callers=30 --log-file=./php.log /usr/bin/php test.php

where test.php is:

<?php
  $obj = new TestExtension("testing");
  echo $obj->test() . "\n";
?>

then the latter gives me a whole bunch of errors, all of which are:

Address 0xe4c3e98 is 24 bytes inside a block of size 66 free'd

The valgrind summary is as follows:

126 ==23067== HEAP SUMMARY:
127 ==23067==     in use at exit: 9,031 bytes in 15 blocks
128 ==23067==   total heap usage: 25,131 allocs, 25,116 frees, 4,435,757 bytes allocated
129 ==23067==
130 ==23067== LEAK SUMMARY:
131 ==23067==    definitely lost: 0 bytes in 0 blocks
132 ==23067==    indirectly lost: 0 bytes in 0 blocks
133 ==23067==      possibly lost: 0 bytes in 0 blocks
134 ==23067==    still reachable: 9,031 bytes in 15 blocks
135 ==23067==         suppressed: 0 bytes in 0 blocks
136 ==23067== Rerun with --leak-check=full to see details of leaked memory
137 ==23067==
138 ==23067== For counts of detected and suppressed errors, rerun with: -v
139 ==23067== ERROR SUMMARY: 48 errors from 5 contexts (suppressed: 0 from 0)
2
  • missing a comma after myClass->test() in RETURN_STRING() ? Commented Jan 21, 2015 at 11:03
  • 1
    There is no missing comma. Commented Jan 21, 2015 at 11:16

1 Answer 1

2

Although it's not stated in the documentation for the RETURN_STRING macro (which seems like an omission: it should definitely state lifetime requirements) it would appear that the macro expands to more than one line.

Say, for example:

RETURN_STRING(myclass->test().c_str(), 1);

becomes:

const char* arg = myclass->test().c_str();
someCode();
someMoreCode();
finalCode(arg);

arg is not valid on the last line because (assuming it returns by value) the temporary result of myclass->test() only exists for the duration of the first line. The result of myclass->test().c_str() is thus, also, only valid for the duration of the first line. arg becomes a dangling pointer immediately thereafter.

Your workaround is a correct way to fix this. I would recommend, when using any of these macros, ensuring that you pass it a pointer to data that is assuredly still in existence for at least as long as the macro runs, no matter how many statements/expressions that may involve.

Here's what I'd do:

if (myclass != NULL) {
   const string& retval = myclass->test();
   RETURN_STRING(retval.c_str(), 1);
}

Now, no matter what myclass->test() is, it'll survive all of the macro's expanded statements, and you've not had to copy it into a new std::string object.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! I should have looked at the macro. I'm kinda still fumbling my way around PHP extensions. Just out of interest, what documentation do you use?
@VanessaDeagan: I don't write PHP extensions. So, none. :)
I write some php extensions, I would suggest github and stackoverflow to search for example code, there is little organised documentation, some of it is in the phpinternals book and some other parts are in the php hacker manual, but yeah, you're going to find documentation hard to find :(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.