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.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.