2

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 *GetString()
{
    std::stringstream ss;
    ss << "The random number is: " << rand();
    return ss.str().c_str();
}

could the c string be destroyed when ss falls off the stack? I'm assuming so...

Another option may be to create a new string on the heap, but what is going to deallocate that?

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

The same goes for pointers to other things as well as strings.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
Fire Lancer
  • 29,364
  • 31
  • 116
  • 182

10 Answers10

8

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.

n-alexander
  • 14,663
  • 12
  • 42
  • 43
3

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.
Martin York
  • 257,169
  • 86
  • 333
  • 562
1

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.

shoosh
  • 76,898
  • 55
  • 205
  • 325
  • Ok, well my dll is actauly for python, so whats the best way to do this? Should I wrap the dll functions up in python functions that say "Call dll function; Call dll deallocate function"? I'm assuming python makes a completly new string, rather than just wrapping an object around it? – Fire Lancer Nov 12 '08 at 08:30
1

Well obviously anytime you are returning pointers to memory allocated inside a function the deallocating must come externally, unless you are using garbage collection. If you don't want to do this, allocate a character buffer b efore calling GetString() and change the prototype to

int get_string(const char* buffer);

Then fill up the buffer. But returning a point to malloced data is fine.

0

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.

Alex
  • 7,728
  • 3
  • 35
  • 62
0

There are various methods, developed over time, to return a variable amount of data from a function.

  1. Caller passes in buffer.
    1. The neccessary size is documented and not passed, too short buffers are Undefined Behavior: strcpy()
    2. The neccessary size is documented and passed, errors are signaled by the return value: strcpy_s()
    3. The neccessary size is unknown, but can be queried by calling the function with buffer-length 0: snprintf
    4. The neccessary size is unknown and cannot be queried, as much as fits in a buffer of passed size is returned. If neccessary, additional calls must be made to get the rest: fread
    5. The neccessary size is unknown, cannot be queried, and passing too small a buffer is Undefined Behavior. This is a design defect, therefore the function is deprecated / removed in newer versions, and just mentioned here for completeness: gets.
  2. Caller passes a callback:
    1. The callback-function gets a context-parameter: qsort_s
    2. The callback-function gets no context-parameter. Getting the context requires magic: qsort
  3. Caller passes an allocator: Not found in the C standard library. All allocator-aware C++ containers support that though.
  4. Callee contract specifies the deallocator. Calling the wrong one is Undefined Behavior: fopen->fclose strdup->free
  5. Callee returns an object which contains the deallocator: COM-Objects std::shared_ptr
  6. Callee uses an internal shared buffer: asctime

In general, whenever the user has to guess the size or look it up in the manual, he will sometimes get it wrong. If he does not get it wrong, a later revision might invalidate his careful work, so it doesn't matter he was once right. Anyway, this way lies madness (UB).

For the rest, choose the most comfortable and efficient one you can.

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
0

If you declare ss as static you can avoid the problem. This could be a good solution if your program runs on a single-thread enviroment.

jab
  • 2,844
  • 1
  • 21
  • 22
  • But the result gets invalid, once the function is called a second time. From that moment on, only the result from the second call is valid. – wimh Nov 12 '08 at 08:58
  • making the scream static will make thing much worse. Are they going to share teh string? Does the stream guarantee the pointer won't change (no)? Will it delete the pointer on a re-assignment (yes)? The first caller will be left with a dead pointer after a second call, etc. No go – n-alexander Nov 12 '08 at 14:25
0

You have to allocate the string on the heap if you want to safely return it, also allocate with malloc() i.s.o. new() when writing C functions.

When you return pointers (and, unlike in C++, in C you have no real choice many times), deallocation is always a concern. There isn't really a definitive solution.

One way of handling this I've seen in quite some API's is calling all function either

CreateString()

When memory needs to be deallocated by the caller, and

GetString()

when that's not an issue.

This is anything but foolproof of course, but given enough discipline it's the best method I've seen to be honest...

Pieter
  • 17,435
  • 8
  • 50
  • 89
0

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.

sep
  • 3,409
  • 4
  • 29
  • 32
  • 1) what is the reason to make out static? 2) you can't return const char* 3) you mastn't return new-ed memory into a C program – n-alexander Nov 12 '08 at 14:19
  • 1) So that the address of the last new-ed memory is retained in between calls to GetString. 2) Ok, agreed that C does not have const. Return char * then. 3) It doesn't matter as long as the C program doesn't call free on it. – sep Nov 17 '08 at 03:30
0

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.

Enno
  • 1,736
  • 17
  • 32