Instead, copy-paste your iffy part to a fresh file and compile for PC (even better if your modules are small so that you can just add a testbench file and link it with the DUT - but don't worry if that's not the case). Generate inputs, and fprintf internal state and outputs into CSV file or similar. You can run on a lot of synthetic inputs you can modify ad hoc, instead of relying in real-world inputs on microcontroller. And investigate results all at once in spreadsheet software, or whatever you like best.
I personally do this for every key algorithm and approach I do, saving each one in their own directory, with an explanatory README text file (and an executable that when run with
-h, will output usage including a short summary of what the executable does). I have literally thousands of these by now.
I don't write those in library form, but in a "fully featured" form, which can be pared down or refactored to what is appropriate for each use case. I often end up taking a look at the documentation, then the code, and then write an algorithmically optimized version with its own test cases, and only after testing, integrate it with the larger whole application/firmware, followed by more testing.
Working with security-sensitive stuff has also "trained" me to switch viewpoints enough so that when I write tests, I can switch to thinking about the code from an adversarial viewpoint, instead of writing the tests to confirm my assumptions as the author of the code. I've caught many a off-by-one and pathological mishandling cases that way, too.
As evidenced by this thread, I personally am definitely no genius; I'm just a good example of how diligent work can get one to the same end point.
As I said before, my default GCC/Clang/ICC settings are
-Wall -O2. For optimization, I learned over two decades ago to not try optimizing even the key parts
perfectly. Instead, I want my code to behave acceptably even for surprisingly pessimistic and pathological inputs. So, I'm not making the best parts as good as possible, but making even the worst parts behave acceptably (for what the code is and does). It can still look like "perfectionism" for a casual observer, but I assure you, it made a huge difference in my development workflow: there is always parts to be enhanced/refactored, but the diminishing returns of that become easier to perceive.
While I tend to use gdb less and less, I find it assuring that I can use it if necessary (even
implementing my own Python accessors). For any kind of tree and graph manipulation, or pointer-based structures, I
very often implement a test case that generates pathological or random structures, and outputs their structure in Graphviz DOT format.
Put simply, I've drifted from using a debugger, to exporting the key internal state in easily accessible and visualizable structures. Some call it "old printf style debugging", but because I do it also to the test cases, I do believe it helps me grasp the algorithms and approaches more thoroughly, and in a more intuitive manner. Plus, it does fit into the good ol' Unix Philosophy, of joining small modular tools to accomplish tasks, instead of using one monolithic tool for everything.