Note that there are some explanatory texts on larger screens.

plurals
  1. POCreating a const char* const* array
    text
    copied!<p>I wish to call a 3rd party library function, which accepts a <code>const char* const*</code> as argument type:-</p> <pre><code>class Arguments { public: Arguments(int argc, const char* const* p_argv = 0); private: deque&lt;std::string&gt; m_Args; } </code></pre> <p>In the library implementations, the passed object is usually a (const) pointer to the first element of <code>char** argv</code>. The strings are then cached in a private member variable (<code>m_Args</code>), so I <em>think</em> the pointers in <code>p_argv</code> only need to be valid for the duration of the construction.</p> <p><strong>Main question:</strong> How can I, or should I create that <code>const char* const* p_argv</code> variable?</p> <hr> <p><strong>More explanation</strong></p> <p>I'm trying to pass objects converted from other array types (Boost Python <code>list</code>s and <code>tuple</code>s) and am trying to find a safe (<a href="http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization" rel="nofollow">RAII</a>) way to do so.</p> <p>I'll be doing this in a number of different places in my code, so I've tried to implement reusable functions that can do the type conversion(s) for me. e.g.</p> <pre><code>// File : converter.hpp ///! ConvertPyListToCharArrayPointer /// /// Convert a Python list to const char* const*. /// \param list - Python list of strings to convert. [in] /// \param pargv - array of const char pointers. [in/out] /// \returns - const pointer to first element in const char* array. const char* const* ConvertPyListToCharArrayPointer( const boost::python::list&amp; list, const char** pargv); </code></pre> <p>I hoped that <code>pargv</code> could be created on the stack, from a function that then calls both <code>ConvertPyListToCharArrayPointer</code> and <code>store_arguments</code>. However, unit-tests on the code show that the stored strings become invalid when restored from the <code>Arguments</code> class. (This makes sense; I allocate the storage for the array of pointers, but not for the strings that those pointers point to).</p> <hr> <p><strong>Converter implementations</strong></p> <p>The original <code>ConvertPyListToCharArrayPointer</code> implementation was:-</p> <p><em>On the stack</em></p> <pre><code>// File : converter.cpp const char* const* ConvertPyListToCharArrayPointer( const boost::python::list&amp; list, const char** pargv) { boost::python::stl_input_iterator&lt;std::string&gt; cur_key(list), end; std::string temp_s; int i=0; while (cur_key != end) { // Get current arg as std::string temp_s = (*cur_key++); // Save string as a const char*, in pargv[i] pargv[i++] = temp_s.c_str(); } pargv[i] = NULL; // Validation code... const char* const* m_argv = &amp;pargv[0]; return m_argv; } </code></pre> <p>To see exactly what was going on, I deployed a load of print statements. Where it says <code>Validation code...</code>, I currently have:-</p> <pre><code> printf("returning address (pargv[0]) of %s (%p)\n", pargv[0], &amp;pargv[0]); printf("pargv[1] of %s (%p)\n", pargv[1], &amp;pargv[1]); printf("pargv[2] of %s (%p)\n", pargv[2], &amp;pargv[2]); printf("pargv[3] of %s (%p)\n", pargv[3], &amp;pargv[3]); if (&amp;pargv[0] == &amp;pargv[1]) printf("addresses of pargv[0] and [1] are the same\n"); </code></pre> <p>With the above <code>ConvertPyList...</code> implementation, given the Python list <code>["foo", "bar", "baz"]</code>, these print statements show:-</p> <pre><code>returning address (pargv[0]) of baz (0x7fffffffa410) pargv[1] of bar (0x7fffffffa418) pargv[2] of baz (0x7fffffffa420) pargv[3] of (null) (0x7fffffffa428) </code></pre> <p>It should obviously show <code>foo</code>, <code>bar</code>, <code>baz</code>, where it instead shows <code>baz</code>, <code>bar</code>, <code>baz</code>.</p> <hr> <p><strong>Getting it to work</strong> - <em><code>malloc</code> and copy the string</em></p> <p>The only way I have been able to get <code>foo</code>, <code>bar</code> and <code>baz</code> to agree, is to call <code>malloc</code> for each <code>char*</code> stored in <code>pargv</code>:-</p> <pre><code>const char* const* pyblast::ConvertPyListToCharArrayPointer( int argc, const boost::python::list&amp; list, const char** pargv) { int i=0; boost::python::stl_input_iterator&lt;std::string&gt; cur_key(list), end; char* cur_ptr; while (cur_key != end) { // Allocate memory on heap. cur_ptr = (char*) malloc( strlen( (*cur_key).c_str() ) ); // Copy string into malloc'd memory if (cur_ptr) strcpy(cur_ptr, (*cur_key).c_str()); else fprintf(stderr, "malloc failure!\n"); // Save pointer. pargv[i++] = cur_ptr; ++cur_key; } pargv[i] = NULL; // Validation code... const char* const* m_argv = &amp;pargv[0]; return m_argv; } </code></pre> <p>But now I have a load of duplicate string's which I need to free myself in the calling code. Is there a safer, RAII kind of way to create this <code>const char* const*</code> array?</p> <p>Perhaps there is a standard type of pointer (in STL or Boost) that could be used here. Using <code>boost::make_shared</code> didn't seem to do the trick...</p> <hr> <p><strong>Unit testing code</strong></p> <p>If you wish to compile, or test the code yourself, the following should help:-</p> <p>I've created the following C++ test file:-</p> <pre><code>/// File: test_converters.cpp #include "converter.hpp" // Easiest just to include the source whilst testing... #include "converter.cpp" #include &lt;boost/python/def.hpp&gt; #include &lt;boost/python/list.hpp&gt; #include &lt;boost/python/module.hpp&gt; // Create and return the Python list: ["foo", "bar", "baz"] boost::python::list make_dummy_list(void) { boost::python::list dummy_list = boost::python::list(); dummy_list.append("foo"); dummy_list.append("bar"); dummy_list.append("baz"); return dummy_list; } int TestPyListConverter(void) { // Create data to be tested. boost::python::list py_argv = make_dummy_list(); ssize_t argc = boost::python::len(py_argv); const char* pargv[argc+1]; //&lt; pointer array const char* const* m_pargv = //&lt; type we're converting to / testing. pyblast::ConvertPyListToCharArrayPointer(argc, py_argv, pargv); // Free pargv, if necessary... /// Tests, from Python perspective:- ssize_t i = 0; // current index on m_pargv char* p_argv; // temporary pointer while (m_pargv[i] != NULL &amp;&amp; i &lt;= argc) { p_argv = PyString_AsString(PyList_GetItem(argv.ptr(), i) ); if( strcmp(m_pargv[i++], p_argv) != 0) { PyErr_SetString(PyExc_Exception, "Test failed. m_pargv[i] != p_argv."); boost::python::throw_error_already_set(); } if (i &gt; 4) // didn't find NULL pointer at end of array. return 1; } return 0; } BOOST_PYTHON_MODULE(test_converter) { boost::python::def("test_py_list_converter", &amp;TestPyListConverter); } </code></pre> <p>N.B. Although this works when using <code>malloc</code>, it looks to me like memory-leaking code, as the pointers in pargv are not <code>free</code>'d. Ideally, the calling code wouldn't need to worry about that...</p> <p>This can be compiled with the command:</p> <pre><code>$ gcc -O1 -shared -g -pipe -fstack-protector -fPIC -I/usr/include/python2.7 -I./ ./test_converters.cpp -o ./test_converter.so -lboost_python -lpython2.7 </code></pre> <p>and executed with:</p> <pre><code>$ python -c 'import test_converter; test_converter.test_py_list_converter()' </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload