Mike’s Place

C++ in Micro Environments

This is a follow-up to my Minimal C++ article. It might be helpful to read that article first, in order to understand the context better.

Here, I present a relatively refined—and featherweight—method for using C++ in “micro” environments: think 16- or 32-bit microcontrollers with memory on the order of 64–256 KiB of storage, usually split between the program and runtime memory (RAM).

Runtime Source

This is the (extremely) minimal C++ runtime, which I’ll refer to later.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void *
operator new(size_t mem) {
    return calloc(1, mem);
}

void *
operator new[](size_t mem) {
    return calloc(1, mem);
}

void
operator delete(void *ptr) {
    free(ptr);
}

void
operator delete(void *ptr, size_t _u) {
    (void)_u;
    free(ptr);
}

void
operator delete[](void *ptr) {
    free(ptr);
}

void
operator delete[](void *ptr, size_t _u) {
    free(ptr);
}

extern "C" void
__cxa_pure_virtual(void) {
    fprintf(stderr, "runtime error: pure virtual function called!\n");
    abort();
}

Keep in mind that this runtime support code is intended to support only the bare minimum required to compile (useful) C++ code. It does NOT provide any support for exceptions or RTTI. If you need those features in your program, the Minimal C++ method won’t work for it; use the full C++ language and a suitable runtime instead.

Application

Given:

— it is possible to write C++ that is very lean. It essentially results in the C++ language being used as syntactic sugar on top of C.

As written, it does require that the C functions calloc(3), free(3), and abort(3) are present. However, the calls to these functions can be replaced by any code that has the same semantic meaning. If the program has a custom heap implementation which ensures that only 4 KiB of the system’s memory available that way, then it makes sense to call the custom allocator’s functions, and not the ones which are provided by the standard C library.

Okay, But Why?

There are several different reasons why one might want to do this:

And of course, there are plenty of other reasons that I haven’t listed here. Regardless of the reason, it is truly this easy to get C++ into a previously C-only code base—and it can be useful, too.

… But C++ is Heavy!

Well, actually, that’s not quite true. Though it is true that most C++ programmers write code that tends to be heavy for one reason or another.

C++—the language, excluding the runtime—is not much heavier than C. In fact, for a lot of code, C++ will generate exactly the same code as C, and in same cases (because it is more knowledgeable about things like context) it can both generate code that is more efficient than the C compiler can, and enable the programmer to make more efficient use of time by reducing repetition.

Virtual Methods

In C++, a class can have methods which are virtual, meaning that they can be overridden by a subclass, and given a pointer or reference to the base class, the virtual function in the most derived type will be called. Methods can also be “pure virtual” methods, meaning that they are part of the interface, but have no implementation in the base class, making the base class abstract. Pure virtual methods must be implemented or an instance of the class cannot be created.

Virtual methods require that the implementation method be found through indirection at runtime, using an apparatus known as the vtable for the object. While this does carry with it a performance impact, it is negligible for nearly all types of code, and compilers will optimize that impact away at every available chance. In other words: it’s no worse than a struct carrying function pointers—and actually it’s better, because:

Templates

One of the most often criticized features of C++ is templates. In C++, it is possible to write a function or a class which is parameterized by a type, and then when it is used with a concrete type, the template is used to instantiate C++ code that works with the concrete type.

That’s probably about as clear as a glass of milk, so I’ll try explaining how it works by example.

This is a template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <typename T>
class array_t {
private:
    size_t  mSize, mUsed;
    T      *mItems;

public:
    array_t() : mItems(nullptr), mSize(16), mUsed(0) {
        mItems = new T[mSize]();
    }

    ~array_t() {
        if(mSize) {
            delete[] mItems;
        }
    }

    T &operator[](size_t idx) {
        return mItems[idx];
    }

    T &operator[](size_t idx) const {
        return mItems[idx];
    }

    // Remainder of implementation not shown here...
};

To actually use this template, code like this is present somewhere:

1
2
3
4
5
void
foo() {
    array_t<uint8_t> bytes;
    bytes[0] = 0x01;
}

Which instantiates the template in the compilation unit in which it is mentioned. If array_t were a regular class here, it would have functions named things like array_t::operator[]. However, it will actually wind up having functions which are named like array_t<uint8_t>::operator[], because it is a template type, and uint8_t is the type it is instantiated for.

The takeaway here? You can have type-safe, memory-managed containers with minimal code, simply by using C++ anywhere you’d use plain C!

Conclusion

I hope that this helps to clarify a bit about the how to use it over the previous Minimal C++ article. I’d like to show some production-quality, non-trivial examples here at some point, hopefully soon.

Thanks for reading.

If you appreciated this article (or anything else I’ve written), please consider donating to help me out with my expenses—and thanks!