Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

60%
+1 −0
Q&A Avoiding checking opcode multiple times in state machine for embedded system

You can replace it with a look-up table ("LUT"). There's a common manual optimization technique which we had to use a lot back in the days. For any switch applied to an enum where the enum values ...

posted 1mo ago by Lundin‭  ·  edited 1mo ago by Lundin‭

Answer
#3: Post edited by user avatar Lundin‭ · 2026-08-10T13:46:06Z (about 1 month ago)
  • You can replace it with a look-up table ("LUT").
  • There's a common manual optimization technique which we had to use a lot back in the days. For any `switch` applied to an `enum` where the `enum` values are in sequence (ideally from 0 to _n_, or at least adjacent), it can be replaced with a branch-free look-up table.
  • That is, instead of a `switch` or a long `if else if`, you can do a single look-up table check. That is, if we take that `enum` from my Q&A and modify it:
  • ```c
  • typedef enum
  • {
  • COFFEE_OK,
  • COFFEE_IDLE,
  • COFFEE_NO_BEANS,
  • COFFEE_NO_WATER,
  • COFFEE_UNEXPECTED,
  • COFFEE_N // lets add this one to the end
  • } coffee_result_t;
  • ```
  • (Or in your case I guess the name might be `OPCODE_N`.)
  • Then for a sequential enum `COFFEE_N` equals the number of enumerations supported.
  • Similarly as I designed a function-pointer jump table in that Q&A, we can also do a data look-up table in that same manner. Create an array (in flash) that contains the outcome for all possible scenarios pre-programmed. Then we don't have to check it in run-time.
  • ```c
  • static const uint8_t OPCODE_results [OPCODE_N] =
  • {
  • [OPCODE_STATUS] = CMD_REQUEST,
  • };
  • ```
  • Done. Huh how can this tiny bit code replace that whole `switch`/`if else if`?
  • In that Q&A, I mentioned that having the "OK" value as number zero is a great idea because that's the default implicit initializer.
  • > What's important here is that the first state in an enum like this should correspond to OK/no errors, with the value 0. This is also a de facto standard way to write programs, so that any non-zero value corresponds to a specialized status/error code.
  • And so we see why that is so nice - now we _don't_ have to write all of this code:
  • ```c
  • [OPCODE_CHANNEL_ON] = CMD_OK,
  • [OPCODE_CHANNEL_OFF] = CMD_OK,
  • [OPCODE_RELAY_ON] = CMD_OK,
  • [OPCODE_RELAY_OFF] = CMD_OK,
  • ```
  • Because those are already set to zero by the default initialization of `OPCODE_results` and zero ought to equal `CMD_OK`. So we just have to create a look-up table where we explicitly initialize all the "not OK" scenarios.
  • And with that done, we can replace the whole `switch` with a simple
  • ```c
  • return OPCODE_results[received_opcode];
  • ```
  • where `received_opcode` is assumed to be verified and sanitized in advance. This is pretty much entirely branch-free and if you keep reusing that same `OPCODE_results[received_opcode]` multiple times inside a function without changing `received_opcode`, then it will likely just get stored in a register. So if you check it twice - no biggie.
  • ---
  • _However_, please note that modern optimizing compilers actually do this very optimization internally and probably go further still. So you shouldn't replace `switch` with a look-up table to increase performance, but rather to make the code more readable and compact. Which may or may not be the case.
  • Also, a MCU which controls a bunch of relays doesn't sound like anything high-end with cache and branch prediction, so you probably don't need to take branching in consideration. But compile-time calculations over run-time do save some CPU no matter what MCU that is used.
  • `switch` does have the advantage of `default` in case you do want some defensive programming there even when the data is sanitized. Defensive programming is encouraged by MISRA C - always have a trailing `else` after `if else if` and a trailing `default` in `switch`. The equivalent of that in the look-up table version is the check that the `received_opcode` is in range of 0 to `OPCODE_N`.
  • You can replace it with a look-up table ("LUT").
  • There's a common manual optimization technique which we had to use a lot back in the days. For any `switch` applied to an `enum` where the `enum` values are in sequence (ideally from 0 to _n_, or at least adjacent), it can be replaced with a branch-free look-up table.
  • That is, instead of a `switch` or a long `if else if`, you can do a single look-up table check. That is, if we take that `enum` from my Q&A and modify it:
  • ```c
  • typedef enum
  • {
  • COFFEE_OK,
  • COFFEE_IDLE,
  • COFFEE_NO_BEANS,
  • COFFEE_NO_WATER,
  • COFFEE_UNEXPECTED,
  • COFFEE_N // lets add this one to the end
  • } coffee_result_t;
  • ```
  • (Or in your case I guess the name might be `OPCODE_N`.)
  • Then for a sequential enum `COFFEE_N` equals the number of enumerations supported.
  • Similarly as I designed a function-pointer jump table in that Q&A, we can also do a data look-up table in that same manner. Create an array (in flash) that contains the outcome for all possible scenarios pre-programmed. Then we don't have to check it in run-time.
  • ```c
  • static const uint8_t OPCODE_results [OPCODE_N] =
  • {
  • [OPCODE_STATUS] = CMD_REQUEST,
  • };
  • ```
  • Done. Huh how can this tiny bit code replace that whole `switch`/`if else if`?
  • In that Q&A, I mentioned that having the "OK" value as number zero is a great idea because that's the default implicit initializer.
  • > What's important here is that the first state in an enum like this should correspond to OK/no errors, with the value 0. This is also a de facto standard way to write programs, so that any non-zero value corresponds to a specialized status/error code.
  • And so we see why that is so nice - now we _don't_ have to write all of this code:
  • ```c
  • [OPCODE_CHANNEL_ON] = CMD_OK,
  • [OPCODE_CHANNEL_OFF] = CMD_OK,
  • [OPCODE_RELAY_ON] = CMD_OK,
  • [OPCODE_RELAY_OFF] = CMD_OK,
  • ```
  • Because those are already set to zero by the default initialization of `OPCODE_results` and zero ought to equal `CMD_OK`. So we just have to create a look-up table where we explicitly initialize all the "not OK" scenarios.
  • And with that done, we can replace the whole `switch` with a simple
  • ```c
  • return OPCODE_results[received_opcode];
  • ```
  • where `received_opcode` is assumed to be verified and sanitized in advance. This is pretty much entirely branch-free and if you keep reusing that same `OPCODE_results[received_opcode]` multiple times inside a function without changing `received_opcode`, then it will likely just get stored in a register. So if you check it twice - no biggie.
  • But obviously look-up tables are execution speed over memory use optimizations. But for MCUs you generally want to do execution speed > RAM use > flash use, where you got a ton of flash anyway.
  • ---
  • _However_, please note that modern optimizing compilers actually do this very optimization internally and probably go further still. So you shouldn't replace `switch` with a look-up table to increase performance, but rather to make the code more readable and compact. Which may or may not be the case.
  • Also, a MCU which controls a bunch of relays doesn't sound like anything high-end with cache and branch prediction, so you probably don't need to take branching in consideration. But compile-time calculations over run-time do save some CPU no matter what MCU that is used.
  • `switch` does have the advantage of `default` in case you do want some defensive programming there even when the data is sanitized. Defensive programming is encouraged by MISRA C - always have a trailing `else` after `if else if` and a trailing `default` in `switch`. The equivalent of that in the look-up table version is the check that the `received_opcode` is in range of 0 to `OPCODE_N`.
#2: Post edited by user avatar Lundin‭ · 2026-08-10T13:43:48Z (about 1 month ago)
  • You can replace it with a look-up table ("LUT").
  • There's a common manual optimization technique which we had to use a lot back in the days. For any `switch` applied to an `enum` where the `enum` values are in sequence (ideally from 0 to _n_, or at least adjacent), it can be replaced with a branch-free look-up table.
  • That is, instead of a `switch` or a long `if else if`, you can do a single look-up table check. That is, if we take that `enum` from my Q&A and modify it:
  • ```c
  • typedef enum
  • {
  • COFFEE_OK,
  • COFFEE_IDLE,
  • COFFEE_NO_BEANS,
  • COFFEE_NO_WATER,
  • COFFEE_UNEXPECTED,
  • COFFEE_N // lets add this one to the end
  • } coffee_result_t;
  • ```
  • (Or in your case I guess the name might be `OPCODE_N`.)
  • Then for a sequential enum `COFFEE_N` equals the number of enumerations supported.
  • Similarly as I designed a function-pointer jump table in that Q&A, we can also do a data look-up table in that same manner. Create an array (in flash) that contains the outcome for all possible scenarios pre-programmed. Then we don't have to check it in run-time.
  • ```c
  • static const uint8_t OPCODE_results [OPCODE_N] =
  • {
  • [OPCODE_STATUS] = CMD_REQUEST,
  • };
  • ```
  • Done. Huh how can this tiny bit code replace that whole `switch`/`if else if`?
  • In that Q&A, I mentioned that having the "OK" value as number zero is a great idea because that's the default implicit initializer.
  • > What's important here is that the first state in an enum like this should correspond to OK/no errors, with the value 0. This is also a de facto standard way to write programs, so that any non-zero value corresponds to a specialized status/error code.
  • And so we see why that is so nice - now we _don't_ have to write all of this code:
  • ```c
  • [OPCODE_CHANNEL_ON] = CMD_OK,
  • [OPCODE_CHANNEL_OFF] = CMD_OK,
  • [OPCODE_RELAY_ON] = CMD_OK,
  • [OPCODE_RELAY_OFF] = CMD_OK,
  • ```
  • Because those are already set to zero by the default initialization of `OPCODE_results` and zero ought to equal `CMD_OK`. So we just have to create a look-up table where we explicitly initialize all the "not OK" scenarios.
  • And with that done, we can replace the whole `switch` with a simple
  • ```c
  • return OPCODE_results[received_opcode];
  • ```
  • where `received_opcode` is assumed to be verified and sanitized in advance. This is pretty much entirely branch-free and if you keep reusing that same `OPCODE_results[received_opcode]` multiple times inside a function without changing `received_opcode`, then it will likely just get stored in a register.
  • ---
  • _However_, please note that modern optimizing compilers actually do this very optimization internally and probably go further still. So you shouldn't replace `switch` with a look-up table to increase performance, but rather to make the code more readable and compact. Which may or may not be the case.
  • Also, a MCU which controls a bunch of relays doesn't sound like anything high-end with cache and branch prediction, so you probably don't need to take branching in consideration. But compile-time calculations over run-time do save some CPU no matter what MCU that is used.
  • `switch` does have the advantage of `default` in case you do want some defensive programming there even when the data is sanitized. Defensive programming is encouraged by MISRA C - always have a trailing `else` after `if else if` and a trailing `default` in `switch`. The equivalent of that in the look-up table version is the check that the `received_opcode` is in range of 0 to `OPCODE_N`.
  • You can replace it with a look-up table ("LUT").
  • There's a common manual optimization technique which we had to use a lot back in the days. For any `switch` applied to an `enum` where the `enum` values are in sequence (ideally from 0 to _n_, or at least adjacent), it can be replaced with a branch-free look-up table.
  • That is, instead of a `switch` or a long `if else if`, you can do a single look-up table check. That is, if we take that `enum` from my Q&A and modify it:
  • ```c
  • typedef enum
  • {
  • COFFEE_OK,
  • COFFEE_IDLE,
  • COFFEE_NO_BEANS,
  • COFFEE_NO_WATER,
  • COFFEE_UNEXPECTED,
  • COFFEE_N // lets add this one to the end
  • } coffee_result_t;
  • ```
  • (Or in your case I guess the name might be `OPCODE_N`.)
  • Then for a sequential enum `COFFEE_N` equals the number of enumerations supported.
  • Similarly as I designed a function-pointer jump table in that Q&A, we can also do a data look-up table in that same manner. Create an array (in flash) that contains the outcome for all possible scenarios pre-programmed. Then we don't have to check it in run-time.
  • ```c
  • static const uint8_t OPCODE_results [OPCODE_N] =
  • {
  • [OPCODE_STATUS] = CMD_REQUEST,
  • };
  • ```
  • Done. Huh how can this tiny bit code replace that whole `switch`/`if else if`?
  • In that Q&A, I mentioned that having the "OK" value as number zero is a great idea because that's the default implicit initializer.
  • > What's important here is that the first state in an enum like this should correspond to OK/no errors, with the value 0. This is also a de facto standard way to write programs, so that any non-zero value corresponds to a specialized status/error code.
  • And so we see why that is so nice - now we _don't_ have to write all of this code:
  • ```c
  • [OPCODE_CHANNEL_ON] = CMD_OK,
  • [OPCODE_CHANNEL_OFF] = CMD_OK,
  • [OPCODE_RELAY_ON] = CMD_OK,
  • [OPCODE_RELAY_OFF] = CMD_OK,
  • ```
  • Because those are already set to zero by the default initialization of `OPCODE_results` and zero ought to equal `CMD_OK`. So we just have to create a look-up table where we explicitly initialize all the "not OK" scenarios.
  • And with that done, we can replace the whole `switch` with a simple
  • ```c
  • return OPCODE_results[received_opcode];
  • ```
  • where `received_opcode` is assumed to be verified and sanitized in advance. This is pretty much entirely branch-free and if you keep reusing that same `OPCODE_results[received_opcode]` multiple times inside a function without changing `received_opcode`, then it will likely just get stored in a register. So if you check it twice - no biggie.
  • ---
  • _However_, please note that modern optimizing compilers actually do this very optimization internally and probably go further still. So you shouldn't replace `switch` with a look-up table to increase performance, but rather to make the code more readable and compact. Which may or may not be the case.
  • Also, a MCU which controls a bunch of relays doesn't sound like anything high-end with cache and branch prediction, so you probably don't need to take branching in consideration. But compile-time calculations over run-time do save some CPU no matter what MCU that is used.
  • `switch` does have the advantage of `default` in case you do want some defensive programming there even when the data is sanitized. Defensive programming is encouraged by MISRA C - always have a trailing `else` after `if else if` and a trailing `default` in `switch`. The equivalent of that in the look-up table version is the check that the `received_opcode` is in range of 0 to `OPCODE_N`.
#1: Initial revision by user avatar Lundin‭ · 2026-08-10T13:43:06Z (about 1 month ago)
You can replace it with a look-up table ("LUT").

There's a common manual optimization technique which we had to use a lot back in the days. For any `switch` applied to an `enum` where the `enum` values are in sequence (ideally from 0 to _n_, or at least adjacent), it can be replaced with a branch-free look-up table.

That is, instead of a `switch` or a long `if else if`, you can do a single look-up table check. That is, if we take that `enum` from my Q&A and modify it:

```c
typedef enum
{
  COFFEE_OK,
  COFFEE_IDLE,
  COFFEE_NO_BEANS,
  COFFEE_NO_WATER,
  COFFEE_UNEXPECTED,

  COFFEE_N  // lets add this one to the end
} coffee_result_t;
```

(Or in your case I guess the name might be `OPCODE_N`.)

Then for a sequential enum `COFFEE_N` equals the number of enumerations supported. 

Similarly as I designed a function-pointer jump table in that Q&A, we can also do a data look-up table in that same manner. Create an array (in flash) that contains the outcome for all possible scenarios pre-programmed. Then we don't have to check it in run-time.

```c
static const uint8_t OPCODE_results [OPCODE_N] =
{
  [OPCODE_STATUS] = CMD_REQUEST,
};
```

Done. Huh how can this tiny bit code replace that whole `switch`/`if else if`?

In that Q&A, I mentioned that having the "OK" value as number zero is a great idea because that's the default implicit initializer. 

> What's important here is that the first state in an enum like this should correspond to OK/no errors, with the value 0. This is also a de facto standard way to write programs, so that any non-zero value corresponds to a specialized status/error code.

And so we see why that is so nice - now we _don't_ have to write all of this code:

```c
[OPCODE_CHANNEL_ON]  = CMD_OK,
[OPCODE_CHANNEL_OFF] = CMD_OK,
[OPCODE_RELAY_ON]    = CMD_OK,
[OPCODE_RELAY_OFF]   = CMD_OK,
``` 

Because those are already set to zero by the default initialization of `OPCODE_results` and zero ought to equal `CMD_OK`. So we just have to create a look-up table where we explicitly initialize all the "not OK" scenarios.

And with that done, we can replace the whole `switch` with a simple 

```c
return OPCODE_results[received_opcode];
```

where `received_opcode` is assumed to be verified and sanitized in advance. This is pretty much entirely branch-free and if you keep reusing that same `OPCODE_results[received_opcode]` multiple times inside a function without changing `received_opcode`, then it will likely just get stored in a register.

---

_However_, please note that modern optimizing compilers actually do this very optimization internally and probably go further still. So you shouldn't replace `switch` with a look-up table to increase performance, but rather to make the code more readable and compact. Which may or may not be the case.

Also, a MCU which controls a bunch of relays doesn't sound like anything high-end with cache and branch prediction, so you probably don't need to take branching in consideration. But compile-time calculations over run-time do save some CPU no matter what MCU that is used.

`switch` does have the advantage of `default` in case you do want some defensive programming there even when the data is sanitized. Defensive programming is encouraged by MISRA C - always have a trailing `else` after `if else if` and a trailing `default` in `switch`. The equivalent of that in the look-up table version is the check that the `received_opcode` is in range of 0 to `OPCODE_N`.