Your question is a little bit unclear. There is no "size" in your code example, I guess you are talking about the size of some internal array of the type "handle_t". I further assume at the time when
handle_init is called, it is not clear how often
handle_do will be called, or which additional parameters to
handle_do will be passed, with different impact on the needed memory.
Actually, from a viewpoint of the user of your API, it should not matter how and where the allocation is done. If "handle_t" is an opaque type, your module should abstract away the internal memory allocation strategy from the user of your module. If and where you alloc, realloc and free any internal data structures should be kept hidden from outside. Under no circumstances the user of the module should have to call
handle_do twice by himself (in an "calculate mode" and an "allocate mode") if the only reason for the double call is the memory calculation. That would unintuive and thus error prone.
So what to do internally, inside of your module? This depends. If I got you right, you are thinking about either doing some realloc in
handle_do, or alternatively doing some of the memory size calculation steps twice, once just for finding out how much memory you are going to allocate, and once for the actual allocation. Both can be viable strategies, and both have some drawbacks. Lots of reallocations can have a negative impact on performance, but doing some time-intensive calculations twice, too, and the latter can make your code more complicated, so there is always a trade-off.
As a compromise, I suggest you check if you can implement your type along the lines of the following example: the
std::vector implementation in C++.
std::vector has a size and a capacity, where the size is always less or equal the capacity. When a vector operation increases the size beyond the capacity, the capacity is automatically increased (by a multiplicative factor, not just by 1!). This will lead to some internal reallocation, but for most real-world cases not too many reallocations. The user of a vector can also ask for more capacity at the "initializiation", so this can reduce the number of necessary reallocations at later point in time.
So in your case: check if
handle_init can do some small allocation beforehand, and if
handle_do notices that the preallocated memory is too small, it will automatically reallocate (with some extra margin if you expect further operations which might need even more internal memory).