简体   繁体   中英

std::list, std::vector methods and malloc()

Working with stl:list and stl::vector types under interrupt handlers I want to avoid malloc() calls.

The question: What is a best way to prevent malloc() calls in STL list and vector? Is it enough to create structure with predefined size and then avoid push/pop/erase calls?

Thank you in advance

STL containers like std::list and std::vector have constructors accepting an Allocator type. By supplying your own allocator instead of using the default you are able to control how the container allocates memory. This option is rarely used, but using your own allocator in a real time environment is a good example of where this feature is useful (and proves that the designers of STL did a very good job).

The requirements for you custom allocator type is described in 20.1.6 in the C++ standard

It sounds like you want to preallocate memory in your initialization code, so that your interrupt handler can avoid heap allocations. I'll assume that the elements you're storing in these containers do not themselves perform any heap allocations, because that would complicate the answer.

You can preallocate memory for std::vector by calling the reserve() method. Methods like push_back() , pop() , insert() , and erase() manipulate the vector's size (the number of elements it currently contains). They only affect the capacity (the number of elements it has room for) when the new size is larger than the current capacity. reserve(x) ensures that the capacity is greater or equal to x , increasing the capacity if necessary. (Also note that the only operation that ever decreases a vector's capacity is swap() , so you don't have to worry about erase() reducing the vector's capacity.)

This approach won't work for std::list , but there's another approach that will: preallocate list elements by inserting them into a "spare" list. Instead of inserting new elements, use the splice() method to move them from the "spare" list to the "primary" list. Instead of erasing elements, use the splice() method to move them from the "primary" list to the "spare" list.

As a testimonial: we use two methods mentioned in other answers at my workplace:

  • custom allocators : for our memory leak tracking system, our instrumenting profiler, and a few other systems, we preallocate and/or "pool" (see eg boost::pool) allocs using a provided Allocator -- usually for std::set or std::map, but the principle is the same for std::list.
  • reserve/resize : for std::vectors, it's very common practice for us to reserve or resize (the difference is important, but both can help avoid future allocs) ahead of time.

Mostly we do these two things to avoid fragmentation, decrease allocator overhead, eliminate the copy-on-grow penalty, etc. But sometimes (especially with eg the instrumenting profiler) we want to absolutely avoid allocation during an interrupt handler.

Usually, however, we avoid issues with interrupts and allocations in other manners:

  • get in/get out : try to avoid doing anything other than setting flags or trivial copies during interrupts; some times a static (or preallocated) buffer is a far better solution than a STL container. Holding interrupts for too long is usually a recipe for disaster.
  • disable interrupts during alloc/free : interrupts are queued up while we're allocating/freeing, instead of being dispatched immediately -- it's a feature of the CPU we're working with. Combined with a policy of selectively increasing the scope of that disabling/queuing (around eg std::list manipulation), we can sometimes get away with a interrupt-handler-as-producer, everything-else-as-consumer model, without overriding the allocator. If we're in the middle of consuming something from a std::list (eg a message received from the network hardware), interrupts are queued for as short a period as possible while pop off a copy of what we're about to process.

Note that lock-free data structures could be an alternative to the second bullet here, we haven't set up and done profiling to see if it would help. Designing your own is tricky business anyway.

Paranoia is the better part of valor for interrupt handlers: if you're not certain what you're doing will work, it's sometimes much better to approach the issue in an entirely different manner.

Just one other thing to add: a const std::vector will not cause allocations. So if your interrupt handling code doesn't change the vector, declare it const and the compiler will make the vector stays the same.

As onebyone.livejournal.com mentioned , the C++ standard does not say anything about interrupt handlers. It does talk about signal handlers, but even then it's a very gray area. About the only thing you can do inside a signal handler that's guaranteed to have well-defined behavior across all conforming C/C++ implementations is assigning to a variable of type sig_atomic_t and returning, eg:

sig_atomic_t flag = 0;

// This signal handler has well-defined behavior
void my_signal_handler(int signum)
{
    flag = 1;
}

int main(void)
{
    signal(SIGINT, &my_signal_handler);

    while(1)
    {
        doStuff();
        if(flag)
        {
            flag = 0;
            actuallyHandleSignalNow();
        }
    }

    return 0;
}

Although in practice, you can almost always get away with doing a little more inside your signal handler.

For std::vector that should be sufficient. I don't think anything guarantees that though. Memory allocation is considered an implementation detail. If you can restrict yourself to a specific size, I suggest going with a simple static array instead. That way, you have a fine-grained control over what exactly happens.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM