<!-- language-all: lang-c -->
There is no need for either effectors or sensors to know about each other or to be organized in registries. There is no need for global state either.

Use dumbed down low level dependency injection. Connect sensors and logic/effectors upon initialization and update each in whatever module/function is convenient.


 // HAL
 struct TemperatureSensor {
 float currentTemperatureCelcius;
 int port;
 }
 void updateTemperature(TemperatureSensor * sensor) {
 sensor->currentTemperatureCelcius = readTemperature(sensor->port);
 }

 struct HeaterEffector {
 float power;
 int port;
 }
 void updatePower(HeaterEffector * effector) {
 writePower(effector->port, effector->power);
 }


 // Logic
 struct PID {
 float * input;
 float * output;
 float target;
 }
 void adjustPID(PID * pid) {
 // TODO: PID
 *pid->output = pid->target - *pid->input;
 }

 void main(int argc, char **argv) {
 // The structures are independent, can be allocated however convenient, no need for registries
 TemperatureSensor temperatureSensor = {0, atoi(argv[1])};
 HeaterEffector heaterEffector = {0, atoi(argv[2])};
 PID pid = {&temperatureSensor.currentTemperatureCelcius, &heaterEffector->power, atof(argv[3])};
 for(;;) {
 // The calls below are completely independent and can be wherever
 updateTemperature(&temperatureSensor);
 adjustPID(&pid);
 updatePower(&heater);
 }
 }

True IoC implies injection of behavior, but for a simple program, injection of state would be enough.

# Disclaimer
The supplied example will break in a multi-threaded environment if floats are misaligned or CPU architecture tears float-sized reads.