Return dynamically allocated memory from C++ to C

前端 未结 10 2096
粉色の甜心
粉色の甜心 2021-01-01 07:10

I have a dll that must be useable from C etc, so I cant use string objects etc as a normal would, but I\'m not sure on how to do this safely..

const char *Ge         


        
相关标签:
10条回答
  • 2021-01-01 07:34

    After the function is called, you will want the caller to be responsible for the memory of the string (and especially for de-allocating it). Unless you want to use static variables, but there be dragons! The best way to do this cleanly is to have the caller do the allocation of the memory in the first place:

    void foo() {
      char result[64];
      GetString(result, sizeof(result));
      puts(result);
    }

    and then GetString should look like this:

    int GetString(char * dst, size_t len) {
      std::stringstream ss;
      ss << "The random number is: " << rand();
      strncpy(ss.str().c_str(), dst, len);
    }

    Passing the maximum buffer length and using strncpy() will avoid accidentally overwriting the buffer.

    0 讨论(0)
  • 2021-01-01 07:35

    Over the years C boiled this down to 2 standard methods:

    • Caller passes in buffer.
      There are three versions of this.
      Version 1: Pass a buffer and a length.
      Version 2: Documentation specifies an expected min buffer size.
      Version 3: Pre-Flight. Function returns the min buffer required. caller calls twice first time with a NULL buffer.
      • Example: read()
    • Use a static buffer that is valid until the next call.
      • Example: tmpname()

    A few non standard ones returned memory that you had to explicitly free

    • strdup() pops to mind.
      Common extension but not actually in the standard.
    0 讨论(0)
  • 2021-01-01 07:38

    The first variant doesn't work because you're returning a pointer into a stack object, which will get destroyed. (More presisely, you return a pointer to a heap memory, whch will have been deleted().) Worse still, it may even work for some time, if nobody's overwriting the memory, making it very hard to debug.

    Next, you can not return a const char* unless you return a pointer to a static string like this:

    const char *GetString()
    {
        return "a static string in DATA segment - no need to delete";
    }
    

    You second variant has the problem of returning memory allocated with new() into a C program that will call free(). Those may not be compatible.

    If you return a string to C, there are 2 way to do that:

    char *GetString()
    {
        std::stringstream ss;
        ss << "The random number is: " << rand();
        return strdup( ss.str().c_str() ); // allocated in C style with malloc()
    }
    
    void foo()
    {
        char *p = GetString();
        printf("string: %s", p));
        free( p ); // must not forget to free(), must not use delete()
    }
    

    or:

    char *GetString(char *buffer, size_t len)
    {
        std::stringstream ss;
        ss << "The random number is: " << rand();
        return strncpy(buffer, ss.str().c_str(), len); // caller allocates memory
    }
    
    void foo()
    {
        char buffer[ 100 ];
        printf("string: %s", GetString(buffer, sizeof( buffer ))); // no memory leaks
    }
    

    depending on you memory handling policy.

    As a rule, you can NOT ever return a pointer or a reference to an automatic object in C++. This is one of common mistakes analyzed in many C++ books.

    0 讨论(0)
  • 2021-01-01 07:39

    The answers so far don't address a very significant issue, namely what to do if the length of the required buffer for the result is unknown and can change between calls, even with the same arguments (such as reading a value from a database), so I'm providing what I consider to be the best way to handle this situation.

    If the size is not known in advance, consider passing a callback function to your function, which receives the const char* as a parameter:

    typedef void (*ResultCallback)( void* context, const char* result );
    
    void Foo( ResultCallback resultCallback, void* context )
    {
         std::string s = "....";
         resultCallback( context, s.c_str() );
    }
    

    The implementation of ResultCallback can allocate the needed memory and copy the buffer pointed to by result. I'm assuming C so I'm not casting to/from void* explicitly.

    void UserCallback( void* context, const char* result )
    {
        char** copied = context;
        *copied = malloc( strlen(result)+1 );
        strcpy( *copied, result );
    }
    
    void User()
    {
        char* result = NULL;
    
        Foo( UserCallback, &result );
    
        // Use result...
        if( result != NULL )
            printf("%s", result);
    
        free( result );
    }
    

    This is the most portable solution and handles even the toughest cases where the size of the returned string cannot be known in advance.

    0 讨论(0)
  • 2021-01-01 07:40

    The first would actually not work because the stringstream deallocates it's space on destruction. So if you try to de-reference that pointer there is a good chance that your program would crash.

    The second option you mention is how it's usually done and the user of the function is required to deallocate the space. IIf this is a C program which uses the function make sure you allocate with malloc() and free with free()

    Another option is to return an address of a static char array. This is relevant if you know in advance a good upper bound to the length. More importantly this should be used ONLY if there is no chance that the function is going to be called from two different threads at the same time because using a static array essentially makes your function non-reentrant.

    0 讨论(0)
  • 2021-01-01 07:41

    If thread-safety is not important,

    const char *GetString()
    {
        static char *out;
        std::stringstream ss;
        ss << "The random number is: " << rand();
        delete[] out;
        char *out = new char[ss.str().size()];
        strcpy(ss.str().c_str(), out);
        return out;//is out ever deleted?
    }
    

    Then the function can take over the responsibility of deallocating the string.

    If thread-safety is important,

    Then the best method is to pass it in as an argument, as in,

    void GetString(char *out, int maxlen);
    

    I observe this is what happens when the old non thread-safe APIs are changed to thread-safe.

    0 讨论(0)
提交回复
热议问题