An entity-component system written in C.
I've used this, or variants on it, for a few projects, including a 48-hour game jam. You can see the source code integrated with a rendering project here, with pictures and a showcase here.
General Principles
The main purpose of the ECS is to allow for locality of related data. Rather than each entity being an object that stores its own data, each system stores the relevant data for all applicable entities. Each system handles memory allocation itself, so systems with different usage patterns can use different memory layouts.
For example, components that are on almost all entities (e.g. position) simply store these in an array indexed by the entity id. In the example of position, there is one array for each component of the position, velocity, and acceleration. Then updates are performed by iterating through these arrays in order, ensuring as much of the consecutively processed data is in the same cache line as possible.
A component that isn't on all entities can be laid out differently to save memory while retaining the in-order iteration. The data is stored in a dense array, so if there are only 200 entities with this component out of 10,000, the array will only be 200 entries long. As processing must be order-independent (to avoid dependency on creation order and for ease of multithreading) the data for these 200 components can be put in an arbitrary order. This array gets iterated in-order for the sake of speed, and it's easy to delete from it with swap-and-drop. Then the entities are mapped to the index in which their data is stored, either with a hashmap or just a flat array. This indirection layer is concealed by the interface, which just hands out short-lifespan component IDs that remember this lookup.
The systems are opaque and don't expose their underlying internal representation so it is easy to change the memory layout for performance reasons without affecting external behaviour.
Entity IDs
Entities themselves store no data, and are only a single 32-bit integer ID handle. They use the usual process of packing together a generation (e.g. 10 bits) with a slot identifier (e.g. 22 bits) - the ratio is easy to change depending on the expected number of simultaneous entities and rate of churn. Checking existence is a single array lookup, to check if the stored current generation for that identifier is the same as the same as for the handle in question.