简体   繁体   中英

Creating a const char* const* array

I wish to call a 3rd party library function, which accepts a const char* const* as argument type:-

class Arguments
{
public:
    Arguments(int argc, const char* const* p_argv = 0);
private:
    deque<std::string> m_Args;
}

In the library implementations, the passed object is usually a (const) pointer to the first element of char** argv . The strings are then cached in a private member variable ( m_Args ), so I think the pointers in p_argv only need to be valid for the duration of the construction.

Main question: How can I, or should I create that const char* const* p_argv variable?


More explanation

I'm trying to pass objects converted from other array types (Boost Python list s and tuple s) and am trying to find a safe ( RAII ) way to do so.

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. eg

// 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& list, const char** pargv);

I hoped that pargv could be created on the stack, from a function that then calls both ConvertPyListToCharArrayPointer and store_arguments . However, unit-tests on the code show that the stored strings become invalid when restored from the Arguments class. (This makes sense; I allocate the storage for the array of pointers, but not for the strings that those pointers point to).


Converter implementations

The original ConvertPyListToCharArrayPointer implementation was:-

On the stack

// File : converter.cpp

const char* const* ConvertPyListToCharArrayPointer(
    const boost::python::list& list, const char** pargv)
{
    boost::python::stl_input_iterator<std::string> 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 = &pargv[0];
    return m_argv;
}

To see exactly what was going on, I deployed a load of print statements. Where it says Validation code... , I currently have:-

    printf("returning address (pargv[0]) of %s (%p)\n", pargv[0], &pargv[0]);
    printf("pargv[1] of %s (%p)\n", pargv[1], &pargv[1]);
    printf("pargv[2] of %s (%p)\n", pargv[2], &pargv[2]);
    printf("pargv[3] of %s (%p)\n", pargv[3], &pargv[3]);
    if (&pargv[0] == &pargv[1])
        printf("addresses of pargv[0] and [1] are the same\n");

With the above ConvertPyList... implementation, given the Python list ["foo", "bar", "baz"] , these print statements show:-

returning address (pargv[0]) of baz (0x7fffffffa410)
pargv[1] of bar (0x7fffffffa418)
pargv[2] of baz (0x7fffffffa420)
pargv[3] of (null) (0x7fffffffa428)

It should obviously show foo , bar , baz , where it instead shows baz , bar , baz .


Getting it to work - malloc and copy the string

The only way I have been able to get foo , bar and baz to agree, is to call malloc for each char* stored in pargv :-

const char* const* pyblast::ConvertPyListToCharArrayPointer(
    int argc, const boost::python::list& list, const char** pargv)
{
    int i=0;
    boost::python::stl_input_iterator<std::string> 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 = &pargv[0];
    return m_argv;
}

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 const char* const* array?

Perhaps there is a standard type of pointer (in STL or Boost) that could be used here. Using boost::make_shared didn't seem to do the trick...


Unit testing code

If you wish to compile, or test the code yourself, the following should help:-

I've created the following C++ test file:-

/// File: test_converters.cpp

#include "converter.hpp"

// Easiest just to include the source whilst testing...
#include "converter.cpp"

#include <boost/python/def.hpp>
#include <boost/python/list.hpp>
#include <boost/python/module.hpp>

// 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];    //< pointer array
    const char* const* m_pargv =  //< 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 && i <= 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 > 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",  &TestPyListConverter);
}

NB Although this works when using malloc , it looks to me like memory-leaking code, as the pointers in pargv are not free 'd. Ideally, the calling code wouldn't need to worry about that...

This can be compiled with the command:

$ 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

and executed with:

$ python -c 'import test_converter; test_converter.test_py_list_converter()'

If your main question is, how to convert a queue to const char* const * , then: A pointer to something is used sometimes to to point to the first element of an array of somethings. So in case of const char* const * , a pointer to the first element of an array with pointers to array to char is probably meant. For every array, there must be some kind of protocol to denote the length of the arrays. In case of const char*, this might be a C string, that is usually terminated with 0. For the array of pointers to pointers to char, 0 can also be used to indicate the end of the array. Back to your question:

You can use a std::vector and initialize it with the values returned by std::string.c_str() for all elements of the queue. Add a last 0-pointer to the vector. Now you can convert the address of the first element of the vector to const char* const *.

Not, that the pointer you get is only valid as long as the vector of pointers and the queue of strings is valid.

std::deque< std::string > input;
std::vector< const char* > v;

for ( std::deque< std::string >::const_iterator i = input.begin(); i != input.end(); ++i )
    v.push_back( i->c_str() );

v.push_back( 0 );

const char* const * p = &v[ 0 ];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM