user3519915
user3519915

Reputation: 129

Return string read from buffer and function without dynamic allocation?

How would I go about returning a string built from a buffer within a function without dynamically allocating memory?

Currently I have this function to consider:

    // Reads null-terminated string from buffer in instance of buffer class.
    // uint16 :: unsigned short
    // ubyte :: unsigned char
    ubyte* Readstr( void ) {
        ubyte* Result = new ubyte[]();

        for( uint16 i = 0; i < ByteSize; i ++ ) {
            Result[ i ] = Buffer[ ByteIndex ];
            ByteIndex ++;

            if ( Buffer[ ByteIndex - 1 ] == ubyte( 0 ) ) {
                ByteIndex ++;
                break;
            };
        };

        return Result;
    };

While I can return the built string, I can't do this without dynamic allocation. This becomes a problem if you consider the following usage:

// Instance of buffer class "Buffer" calling Readstr():
    cout << Buffer.Readstr() << endl;
    // or...
    ubyte String[] = Buffer.String();

Usages similar to this call result in the same memory leak as the data is not being deleted via delete. I don't think there is a way around this, but I am not entirely sure if it's possible.

Upvotes: 0

Views: 575

Answers (2)

glampert
glampert

Reputation: 4421

There are at least three ways you could reimplement the method to avoid a direct allocation with new.

The Good:

Use a std::vector (This will allocate heap memory):

std::vector<ubyte> Readstr() 
{
    std::vector<ubyte> Result;
    for (uint16 i = 0; i < ByteSize; i++) 
    {
        Result.push_back(Buffer[ByteIndex]);
        ByteIndex++;

        if (Buffer[ByteIndex - 1] == ubyte(0)) 
        {
            ByteIndex++;
            break;
        }
    }
    return Result;
}

The Bad:

Force the caller to provide an output buffer and possibly a size do avoid overflows (Does not directly allocate memory):

ubyte* Readstr(ubyte* outputBuffer, size_t maxCount) 
{
    for (uint16 i = 0; i < ByteSize; i++) 
    {
        if (i == maxCount)
            break;

        outputBuffer[i] = Buffer[ByteIndex];
        ByteIndex++;

        if (Buffer[ByteIndex - 1] == ubyte(0)) 
        {
            ByteIndex++;
            break;
        }
    }
    return outputBuffer;
}

The Ugly:

Use an internal static array and return a reference to it:

ubyte* Readstr() 
{
    enum { MAX_SIZE = 2048 }; // Up to you to decide the max size...
    static ubyte outputBuffer[MAX_SIZE];

    for (uint16 i = 0; i < ByteSize; i++) 
    {
        if (i == MAX_SIZE)
            break;

        outputBuffer[i] = Buffer[ByteIndex];
        ByteIndex++;

        if (Buffer[ByteIndex - 1] == ubyte(0)) 
        {
            ByteIndex++;
            break;
        }
    }
    return outputBuffer;
}

Be aware that this last option has several limitations, including possibility of data races in multithreaded application and inability to call it inside a recursive function, among other subtle issues. But otherwise, is probably the closest to what you are looking for and can be used safely if you take some precautions and make some assumptions about the calling code.

Upvotes: 1

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153955

Personally, I'd recommend just return std::string or std::vector<T>: this neatly avoids memory leaks and the string won't allocate memory for small strings (well, most implementations are going that way but not all are quite there).

The alternative is to create a class which can hold a big enough array and return an object that type:

struct buffer {
     enum { maxsize = 16 };
     ubyte buffer[maxsize];
};

If you want get more fancy and support bigger strings which would then just allocate memory you'll need to deal a bit more with constructors, destructors, etc. (or just use std::vector<ubyte> and get over it).

Upvotes: 5

Related Questions