4

I have a situation where I will need an amount of memory determined at runtime to pass to a function. I am using a larger buffer on the stack, then only creating on the heap the space that is necessary:

Foo largeBuf[1024];

int sizeUsed = fillBuff(largeBuf, 1024);

Foo* smallerBuf = new Foo[sizeUsed];

for (UINT i = 0; i < sizeUsed; i++)
{
 smallerBuf[i] = largeBuf[i];
} 

Is there a better way to do this? Some standard array copying function?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Nick Heiner
  • 119,074
  • 188
  • 476
  • 699
  • 1
    Do you really mean `Foo* largeBuf[1024]` or should it be `Foo largeBuf[1024]` (just looking ahead to `smallerBuf[i] = largeBuf[i]`) ? – CB Bailey Jul 08 '10 at 19:27
  • I also notice that you're passing an array to fillBuff without passing the array's size. That's a recipe for a buffer overrun. Have you considered just using a std::vector instead? – Peter Ruderman Jul 08 '10 at 19:28
  • @Peter @Eclipse: I suspect he's using some Win32 function. Those have well-documented limits and often return the number of elements actually copied back. Of course we don't actually know this. – GManNickG Jul 08 '10 at 19:31
  • Actually, I am passing the size of largeBuf. Sorry about leaving that out. Good catch. – Nick Heiner Jul 08 '10 at 19:37

7 Answers7

6

You should probably be using an std::vector, which you can initialize directly from the elements of the larger buffer:

std::vector<Foo> smallerBuf(largeBuf, largeBuf+sizeUsed);
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • The method I'm using requires an array. Can a vector be used polymorphically in place of an array? – Nick Heiner Jul 08 '10 at 19:38
  • 1
    Once filled, you can use &smallerBuff[0] in place of an array: http://stackoverflow.com/questions/247738/is-it-safe-to-assume-that-stl-vector-storage-is-always-contiguous – Eclipse Jul 08 '10 at 19:39
5

Firstly, you should be using std::vector. There's no reason not to use it. Then use std::copy:

// surely the pointer was an accident
Foo largeBuf[1024];

// int? design should be changed to use an unsigned type
int sizeUsed = fillBuff(largeBuf, 1024); 

// vector, no memory leaks
std::vector<Foo> smallerBuf;
smallerBuf.reserve(sizeUsed);

// copy
std::copy(largeBuf, largeBuf + sizeUsed, std::back_inserter(smallerBuf));

Or just cut to the chase at the end with:

std::vector<Foo> smallerBuf(largeBuf, largeBuf + sizeUsed);

Or another approach:

std::vector<Foo> buf(1024); // this replaces your stack array
buf.resize(fillBuff(&buf[0], 1024)); // copies directly into it, resizes

Note after this last approach, the memory will still be in use. You can force the capacity to shrink with the copy-swap trick:

template <typename T, typename A>
void shrink_to_fit(std::vector<T, A>& pVector)
{
    std::vector<T, A>(pVector).swap(pVector);
}

// ...

shrink_to_fit(buf);

Most ideal, fillBuf would have a way (or another function) to just return the number of elements it will return given a buffer. Then you can just do:

std::vector<Foo> buf(buffSize()); // or whatever
fillBuff(&buf[0], buf.size());
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • You need a `back_insert_iterator`, not `smallerBuf.begin()` (unless you meant `resize` instead of `reserve`). – CB Bailey Jul 08 '10 at 19:28
  • @Charles: Nope, I meant `reserve` and just forgot to use `back_inserter`. :) Thanks. – GManNickG Jul 08 '10 at 19:30
  • "There's no reason not to use [a vector]" I believe it is overkill, and `fillBuff()` takes an array, not a vector. Are these objections invalid? – Nick Heiner Jul 08 '10 at 19:40
  • @Rosarch: I doubt you have any valid reason to think it's "overkill". It's part of the language! :) And `vector` is just a safe wrapper around a dynamic array, observe my last two methods for passing an array. It should be noted that neither you or I are passing arrays: we're passing pointers to the first element and the number of elements located at that address. So a dynamic array or automatic array work the same. – GManNickG Jul 08 '10 at 19:44
  • A vector is never overkill... and as other answers state, just use the filled array to populate a vector. – rubenvb Jul 08 '10 at 19:47
4

Some standard array copying function?

You mean other than std::copy()?

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • Looks like `std::copy()` is for STL containers. What about raw arrays? – Nick Heiner Jul 08 '10 at 19:39
  • It should work as well because an array is iterateable as far as I remember. Just try it and see – the_drow Jul 08 '10 at 19:44
  • @Rosarch: `std::copy` just requires types that support iterator operations. Pointers work there as well. (Specifically, pointers act like random-access iterators.) You'll find a lot of people make utility functions to make it easier to treat automatic arrays as containers (like `endof(arr)` returning a pointer to the end). But there is `boost::/std::array` for the whole she-bang. (Quite a simple class.) – GManNickG Jul 08 '10 at 19:46
  • 1
    @Rosarch - incorrect. It works on iterators, not containers. BIG difference. – Edward Strange Jul 08 '10 at 19:47
1

I'd think the fastest way would be to use memcpy:

const int BUFSIZE = 1024;

Foo* largeBuf = new Foo[BUFSIZE];

int sizeUsed = fillBuff(largeBuf, BUFSIZE);

Foo* smallerBuf = new Foo[sizeUsed];

memcpy(smallerBuf, largeBuf, sizeUsed * sizeof(Foo));

memcpy_s would be safer if on Windows platform:

memcpy_s(smallerBuf, sizeUsed, largeBuf, sizeUsed * sizeof(Foo));
Avalanchis
  • 4,500
  • 3
  • 39
  • 48
  • 1
    It doesn't offer any advantage over `std::copy`, and loses the ability to copy non-POD's. Might as well consistently use `std::copy` throughout the program. (And use `std::vector`!) – GManNickG Jul 08 '10 at 19:38
0

In C++ you could use std::copy. If your function doesn't modify an array you could pass a const pointer and a size without copying array: some_func( largeBuf, sizeUsed );

Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
0

One idiom you see a lot is to call fillBuff twice. Once to get the size, and once to to the work. Often it's fairly trivial for fillBuff to calculate the needed buffer size:

int sizeUsed = fillBuff(NULL, 0);
Foo* buffer = new Foo[sizeUsed];
fillBuff(buffer, sizeUsed);

If that's not the case, I'd recommend going with a vector:

vector<Foo> buffer;
fillBuff(buffer);

Or, if you must resort to allocating twice, at the very least, specify the size of your buffer:

Foo largeBuf[1024];
int sizeUsed = fillBuff(largeBuf, 1024);

Foo* smallerBuf = new Foo[sizeUsed];
std::copy(largeBuf, largeBuf + sizeUsed, smallerBuf);
Eclipse
  • 44,851
  • 20
  • 112
  • 171
0

How often is this going to occur? If this is going to be in a tight loop, there are lots of different options to consider. You could create stack or heap in advance and do some memory-management yourself. Or you could just pass pointers and share the data if you can do so safely without editing it unknowingly.

If this is a very time-sensitive thing, there are many different implementations, though I'd stay away from creating more layers of objects around things if you don't need to. And if you are filling memory with zeroes, check for implementation speed differences between malloc and setting things to 0 vs just calloc. Or system-specific things like bzero() that sometimes make use of hardware-specific memory commands under-the-hood. Also beware memcpy() vs memmove() - determine which is necessary.

For a bit of fun trivia: http://en.wikipedia.org/wiki/Duff%27s_device :-)

eruciform
  • 7,680
  • 1
  • 35
  • 47