tags:

views:

1379

answers:

7

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

A: 

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.

Mehrdad Afshari
vector will be allocated dynamically,. no matter what you do, as will the things it contains.
anon
Neil: If you have a vector with some preallocated items and you change an item, it won't result in memory allocation (this is not guaranteed by standard though, as I said).
Mehrdad Afshari
Post some code that does what you suggest. Any use of vector (apart possibly from creating an empty one) will result in dynamic allocation. This is guaranteed by the standard.
anon
`/*outside interrupt handler (this *does* result in allocation):*/ vector<int> x; x.push_back(10); /*inside interrupt handler (not likely to allocate anything:*/ x[0] = 0;
Mehrdad Afshari
That in no way addresses the question.
anon
Neil: That was my understanding of *Is it enough to create structure with predefined size and then avoid push/pop/erase calls?* in the OP. I might be wrong however.
Mehrdad Afshari
Creating a vector of predefined size will result in dynamic allocation, either using new or whatever mechanism a user-supplied allocator provides for the vector and the things it contains - there is no way round this!
anon
Of course. But you could **already have** the vector created somewhere else and just alter its contents in the interrupt handler.
Mehrdad Afshari
If it is already created, it must have been created somehow! Vectors are inately dynamic - there is nom way of creating a vector that (for example) does static allocation for its internals. And if you alter its internals, you are open to more dynamic allocations.
anon
It'll be created **outside** the interrupt handler where it's free to call `malloc` and other dynamic allocations. For example, think of a scenario that a `vector` represents a kernel process table where the interrupt handler would just set the process state flag from to `blocked`. I'm not saying that creating a vector with a predefined size doesn't result in dynamic allocation, from that sentence in the OP, I think he already has a preallocated vector to use. If he wants to **create** that in the interrupt handler, then yes, it'll result in dynamic allocation.
Mehrdad Afshari
Sorry, you are right - I've realised I've completely misunderstood the question :-(
anon
+14  A: 

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

Martin Liversage
+1  A: 

Interrupts are not mentioned in the C++ standard (although signals are), so the standard cannot tell you which operations are safe in interrupt context. The situation is in essence the same as for thread-safety -- you need to consult your implementation for what additional guarantees it offers relating to the new concepts it introduces.

In practice, I expect that the operations on vector which are guaranteed to leave iterators valid will not allocate or free any memory (assuming of course that assigning values to the members of the vector does not itself allocate memory in a copy constructor or operator=). They certainly won't re-allocate the vector's internal storage buffer: the standard guarantees this in terms of when "reallocation" is or is not permitted to occur. There's nothing else you'd expect vector to need allocated memory for. If you ensure ahead of time that the vector has sufficient capacity, then once you're in interrupt context you can push_back elements, or assign to them, and guarantee that the vector will not reallocate its contiguous storage, and therefore have strong reason to believe it won't allocate any more memory.

What it might do, though, is cause assigned virtual address space to be committed that was not previously committed. I don't know whether or not that's safe in interrupt context. Ensuring ahead of time that the vector has sufficient size, not just capacity, should mean (with a suitable constructor of course) that all the memory is written to and hence committed.

list is different: it has no excess capacity, and can allocate and free list nodes as items are added and removed. I'd expect an intrusive list to be usable in interrupt context, though, or you could create a list of "zero objects" and then assign to the elements, in the same way you can with vector.

Still, that's just based on expected/typical implementations. The standard doesn't prevent vector from allocating and freeing a few blocks just for laughs, or for obscure platform-related reasons.

Steve Jessop
+3  A: 

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.

xtofl
A: 

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, e.g.:

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.

Adam Rosenfield
+3  A: 

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.

bk1e
Neat, I hadn't thought of using splice() this way.
leander
+3  A: 

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 e.g. 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 e.g. 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 e.g. 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 (e.g. 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.

leander
Please note that a lot of this is *very* architecture- and compiler-specific! It may not apply to many people...
leander