创建一个const char * const *数组

时间:2013-04-11 10:27:41

标签: c++ pointers boost-python arrays

我希望调用第三方库函数,该函数接受const char* const*作为参数类型: -

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

在库实现中,传递的对象通常是指向char** argv的第一个元素的(const)指针。然后将字符串缓存在私有成员变量(m_Args)中,因此我认为 p_argv中的指针只需要在构造期间有效。

主要问题:我该如何或应该创建const char* const* p_argv变量?


更多解释

我正在尝试传递从其他数组类型(Boost Python listtuple s)转换的对象,并且我正在尝试找到一种安全(RAII)方法

我将在我的代码中的许多不同位置执行此操作,因此我尝试实现可以​​为我执行类型转换的可重用函数。 e.g。

// 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);

我希望可以在一个函数中创建pargv,然后调用ConvertPyListToCharArrayPointerstore_arguments。但是,代码的单元测试表明,从Arguments类恢复时,存储的字符串变为无效。 (这是有道理的;我为指针数组分配存储空间,但不为这些指针指向的字符串分配存储空间。)


转换器实施

原始的ConvertPyListToCharArrayPointer实施是: -

在堆栈上

// 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;
}

为了确切了解发生了什么,我部署了一堆打印语句。如果它显示Validation code...,我目前有: -

    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");

使用上面的ConvertPyList...实现,给定Python列表["foo", "bar", "baz"],这些打印语句显示: -

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

它显然应显示foobarbaz,而是显示bazbarbaz


让它发挥作用 - malloc并复制字符串

我能够foobarbaz同意的唯一方法是为malloc中存储的每个char*拨打pargv {1}}: -

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;
}

但是现在我有一堆重复的字符串,我需要在调用代码中释放自己。是否有更安全的RAII方法来创建此const char* const*数组?

也许在这里可以使用标准类型的指针(在STL或Boost中)。使用boost::make_shared似乎没有做到这一点......


单元测试代码

如果您希望自己编译或测试代码,以下内容应该有所帮助: -

我创建了以下C ++测试文件: -

/// 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);
}

N.B。虽然这在使用malloc时有效,但它看起来像内存泄漏代码,因为pargv中的指针不是free'd。理想情况下,调用代码不需要担心......

可以使用以下命令编译:

$ 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

并执行:

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

1 个答案:

答案 0 :(得分:1)

如果您的主要问题是,如何将队列转换为const char * const *,那么:有时使用指向某事物的指针来指向某些数组的第一个元素。因此,在const char * const *的情况下,指向数组的第一个元素的指针可能意味着指向数组到char的指针。对于每个阵列,必须有某种协议来表示数组的长度。在const char *的情况下,这可能是一个C字符串,通常以0结尾。对于指向char的指针数组,0也可用于指示数组的结尾。回到你的问题:

您可以使用std :: vector并使用std :: string.c_str()为队列的所有元素返回的值对其进行初始化。向向量添加最后一个0指针。现在,您可以将向量的第一个元素的地址转换为const char * const *。

不是,只要指针向量和字符串队列有效,你获得的指针才有效。

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 ];