User Tools

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
user:embedded_c_code_doesn_t_have_to_be_ugly [2020/05/11 17:43] – [4. Bit manipulations vs. structured data] Igor Yefmovuser:embedded_c_code_doesn_t_have_to_be_ugly [2022/04/04 23:32] (current) – external edit 127.0.0.1
Line 69: Line 69:
  
 As such (barring external dependencies) I always advise on using the latest stable language standard supported by the toolchain that your organization is comfortable with. And yes, that means that engineers must continually improve their grasp of the language and be on top of the latest stable standard to efficiently take advantage of the improvements provided by that standard. As such (barring external dependencies) I always advise on using the latest stable language standard supported by the toolchain that your organization is comfortable with. And yes, that means that engineers must continually improve their grasp of the language and be on top of the latest stable standard to efficiently take advantage of the improvements provided by that standard.
-==== 3. #define (and const) vs. enum ====+==== 3. #define (and const) vs. enum and magic numbers ====
 So much has been said about the many advantages of pushing as much work as possible away from the preprocessor and into the compiler that is amazes me to still see tons of ''#define'''s in the modern code where language standard features would provide numerous benefits over the plain old text-based code processing that knows nothing of the code structure or the data types used. So much has been said about the many advantages of pushing as much work as possible away from the preprocessor and into the compiler that is amazes me to still see tons of ''#define'''s in the modern code where language standard features would provide numerous benefits over the plain old text-based code processing that knows nothing of the code structure or the data types used.
  
Line 96: Line 96:
  
 <code C>enum{ s2r_uvc_stream_buf_size = s2r_ep_bulk_video_pkts_count * s2r_ep_bulk_video_pkt_size };</code> <code C>enum{ s2r_uvc_stream_buf_size = s2r_ep_bulk_video_pkts_count * s2r_ep_bulk_video_pkt_size };</code>
 +
 +=== Magic numbers ===
 +You know, that kind:
 +<code C>if(interface == 3){</code>
 +
 +Compare that to this code and tell me - which one makes you understand what that code does?
 +<code C>if(interface == s2r_id_audio_stream_ifc){</code>
  
 ==== 4. Bit manipulations vs. structured data ==== ==== 4. Bit manipulations vs. structured data ====
Line 231: Line 238:
 Not only the code becomes that much more readable - it also opens up opportunities for compilers to do platform-specific optimizations which are otherwise just not available to them as the intent of the code is not clearly expressed with all that manual bit shifting... Not only the code becomes that much more readable - it also opens up opportunities for compilers to do platform-specific optimizations which are otherwise just not available to them as the intent of the code is not clearly expressed with all that manual bit shifting...
 ==== 5. Globals ==== ==== 5. Globals ====
 +Having global variables is absolutely the major contributor to entangling unrelated blocks of code and making sure no human mind can fully comprehend your text without first truly understanding **all of it**. "A variable is declared in one file, set in another, accessed in yet another... what fun it is to trace its lifetime!" especially on an embedded system that lacks "usual" debugging tools like stepping through the lines or examining the state of variables.
 +
 +There are a few kinds of globals, IMO:
 +=== "Global" globals ===
 +These are the ones declared at file level and mentioned with an ''extern'' keyword in header files. In my experience almost all of these can be safely made non-globals with little to no impact on the overall code structure and quite often moved into function scopes and passed around as parameters on a stack((more on stack usage later)).
 +
 +See if some of those globals can be made file-scoped by grouping related functions together((naturally all the functions that access the same set of globals are very likely to be related to each other in functionality)). Some ''const''((some are not even declared as ''const'' at times!)) globals can even be turned into ''enum'''s((and stop taking precious RAM or ''.data'' (sometimes ''.text'') segment space)). And most often those globals can, and should, be safely made function-scope.
 +
 +=== Function-level globals ===
 +A relic of K&R style ''C'' language, that required all the function variables be declared up-front, still finds its proponents even in the modern world of not-dumb-anymore-compilers that are able to deduce a lot more information from the code's text than some 50 years ago.
 +
 +These same compilers are capable of making quite intelligent choices on how to translate your code into zeroes and ones, so that you should truly be not afraid of expressing your functionality in the language of abstraction, rather than writing in ''C'' like it is a macro-Assembler((I'm absolutely not advocating for total ignorance on how your ''C'' code translates into machine code, of course not. Just saying that one must **not** try to "help" compilers too much at expense of code's clarity)).
 +
 +Stack allocation is extremely cheap, even on the most low-end down-to-earth hardware, so concerning yourself with the cost of that operation by grouping all-the-possible-function-variables together at the top should be reserved only for the most critical code((in which case it is very likely that such code should be written directly in Assembler by a highly-skilled engineer who is also extremely familiar with the target platform)).
 +
 +=== Cross-function globals ===
 +Oh, these are absolutely the worst, as they tend to sneak up on you at the least expected time and place! They get declared as globals and then are only set in one place while being accessed in plenty of other places. Especially dangerous ones cross thread boundaries, making for extra-nasty race condition type bugs.
 +
 +Even if one doesn't do anything about the first two kinds, one must always strive to eliminate these ones. In the long run it **never** makes sense to have that kind of (questionable) optimization benefit at the expense of destabilizing your code so badly.
  
 ==== 6. Code blocks' nesting ==== ==== 6. Code blocks' nesting ====
 +Somewhat related to the general question of "how many LoC in a function is too many?" this one goes back to gut feeling that cramming all the code into a single function will somehow have positive optimization benefits overall. Let me be clear here: **it doesn't**. That perception is stemming from the fact that function calls in ''C'', while being very cheap, are not zero-overhead operations.
  
 +This perception completely ignores all the advances made in CPU manufacturing in the past 60 years or so((various speculative execution techniques: branch prediction, prefetching, pipelining, value prediction, code and data caching to name a few)).
 +
 +Just write the code in manageable (one-two page length) chunks, packaged as individual functions. And if there really **is** a concern for stack depth you always have the option of inlining those functions((a very often overlooked method to my surprise)).
 +
 +If you have a long-ass ''switch'' statement - make it into a function. If the branches of your ''if'' or ''case'' statements are large - make them into functions. If there's an overgrown block of code that is responsible for one clearly defined operation - make it into a function. If you find yourself writing an ''if'' inside a ''case'' or a similar situation - extract the code block into a function.
 +
 +And as a general rule: if you see a block of code that doesn't fit onto one screen (or your prefrontal cortex, for that matter) - make it into a function!
 +
 +Think of functions as text paragraphs: if you read some text that consists of one huge paragraph chances are you'll give up soon enough and look for some other source of information. One that is made for human consumption.
 +
 +=== Stack depth ===
 +Speaking of stack depth: partitioning your code into smaller functions may indeed //reduce// your stack requirement as you won't be needing to allocate every-single-variable-ever-used-in-any-branch in one chunk, but instead only use up as much stack as needed for each individual function.
 ==== 7. Code comments ==== ==== 7. Code comments ====
 +<code C>CyBool_t isUsbConnected = CyFalse; /* Whether USB connection is active. */
 +
 +int i = 1;   // set i to 1
 +
 +i++;         // increment i by 1</code>
  
 +The comments above are utterly useless. Not only they don't anything to what is already expressed in the code((which is bad enough on its own)), there's a high potential for these types of comments to become stale and ultimately very misleading.

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information