2

Let's say we have an opaque type handle_t that is managed through a simple interface with functions such as:

handle_t *handle_init(void);
int handle_do(handle_t *);
void handle_free(handle_t *);

The problem is that the size cannot be always determined in the initialization step and it has to be calculated in handle_do. One solution I have seen is calling handle_do two times, the first of which returns the size needed to allocate, but doesn't that affect the performance?

Is it a bad practice to allocate or reallocate memory in handle_do? Does it even matter when using handle types (since the init/free routines are made clear)?

2
  • can you provide more code example?
    – Bryan Chen
    Commented Sep 10, 2014 at 4:55
  • 7
    This looks to me like it applies almost exclusively to C, not C++. In C++, instead of handle_init, handle_do and handle_free, you almost certainly want an object with init handled in the ctor, free handled in the dtor, and do handled in a member function--possibly but not necessarily an operator(). Allocation probably should be internal to the object, likely via a std::unique_ptr or some other smart pointer type. Commented Sep 10, 2014 at 6:12

3 Answers 3

2

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++.

A 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).

0

One solution I have seen is calling handle_do two times, the first of which returns the size needed to allocate, but doesn't that affect the performance?

Asking about performance like this, is the wrong question to ask. Consider implementing it, deciding on your performance requirements, measuring performance, and if it is indeed a problem, you can consider implementing a workaround.

I have seen the "call API twice, once for parameters for the second call" only in the Windows API. In particular, the choice to call APIs twice like that, is bad for clients (in the Windows API, getting a damn error string, looks like writing half a novel of code). It also breaks SRP.

Ideally, you should optimize your design for the opposite (make client code easier to write, at the expense of the API implementation - if you have to).

Is it a bad practice to allocate or reallocate memory in handle_do?

No.

Does it even matter when using handle types (since the init/free routines are made clear)?

No, it doesn't. You just have to remember when testing it, to check for invalid combinations of APIs (i.e., add tests like calling handle_init, handle_free, handle_do, in this order).

The problem is that the size cannot be always determined in the initialization step and it has to be calculated in handle_do.

Is the size visible in client code? Does this size matter in client code? If the answer to both questions is "no", then the size is just an implementation detail (i.e. it shouldn't matter in client code). If the answer is "yes" to both questions, you should probably add an API for computing this size as a separate operation.

0

All types have their size determined in compile-time. For handle_t to change it's size in runtime, the data needs to be outside of the type system. std::vector is one way to move your data outside the type system, since it keeps separate memory area for heap-allocated array. The array does not have any type attached to it, only the items have a type. sizeof(std::vector<int>) does not contain the contents of the array.

Given these restrictions, you need to store a pointer to variable-sized memory block that needs to be reallocated when the size changes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.