Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

Compiled from online information, Author: Xiao Ma Er

I can’t remember when I started frequently taking on recruitment work for companies, focusing on the recruitment of industrial embedded product R&D personnel.

Out of a responsible attitude towards both the company and newcomers, I tend to look for candidates who have solid foundational knowledge and an interest in embedded programming, as this benefits both corporate development and personal growth. Unfortunately, many times it feels like searching for a needle in a haystack, arriving with hope and leaving in disappointment.

This has been a very troubling issue for me, and it reminds me of a story I once heard: a foreigner came to a company in China for an inspection and first noticed the employees’ work attitudes, complaining that such employees should all be fired; then during lunch, he tasted the company’s meal and started criticizing the company again.

Looking back at the current university education model, many schools still follow a similar rote-learning approach as in middle and high school. Most students just pass their days with eating, drinking, and playing, and even if the freshmen have some passion at first, they quickly become tainted by the environment. Then, there’s mutual blame among students, schools, and companies, creating a chaotic scene with a lot of negativity.

A good friend of mine teaches computer science at a 985 university in China. Once, during a chat, he complained that many students take a long time to get hands-on experience after starting work, as the training period is lengthy. However, his viewpoint is that this should be expected since what can be learned in university is limited, and real training only happens in the workplace. I was left speechless…

Should it really be this way? I feel lost, but seeing the hard work of the parents behind each student to pay for their tuition and the hopeful eyes they have for their children, I believe we can do better and should do better.

To let everyone experience my inner turmoil, let’s first talk about the recruitment test questions. Previously, I participated in a recruitment process at ZTE, where the recruiter asked me a question: int n = 3, 4; I said I didn’t know, that it was a bizarre usage, and that we shouldn’t use it this way, nor should you ask such a question. I might have been a bit too passionate at that time, which probably infuriated the recruiter, and of course, I didn’t get the job at ZTE. Learning from past lessons, I later established a basic principle when organizing recruitment: only knowledge that is frequently used in work will be considered for assessment.

To discover some good candidates, we have been thinking about what kind of test questions can better assess the abilities and interests of job seekers. Initially, the approach was to ask some basic concepts from foundational courses and specialized courses to filter out those with weak foundational knowledge; then, I hoped the candidates could explain a program they had written out of interest, and through their descriptions, I would delve deeper to determine their specific level of ability.

Unfortunately, 99% of people cannot articulate a program they wrote out of interest; when encountering a few who make stuff up, it becomes obvious after just a few sentences. Later, I had to shelve that question and lower the difficulty, instead providing some programming examples with single-layer loops and moderate judgment (such as counting the number of zeros in an integer, etc.). If, during each recruitment, we could find one or two students who can quickly write such programs, everyone would be excited for half a day.

This is the general capability of students we can recruit; in my view, to reach this level of ability, one only needs to learn programming for a few days randomly. Why do we need four years of university for that? It’s frustrating. But a more realistic problem is how to use such people once they are recruited into the company. We cannot directly let novice personnel practice on products; everyone knows that products are to be sold to users, and initial small problems can become big problems when they reach users.

Training in a company is different from school education; no one will spoon-feed you, and don’t expect someone to guide you hand in hand. Most of the time, it’s a free-range approach, at most giving a few pointers and then you have to read books on your own. This situation also leads to many people initially struggling to get started, and some are even forced to change careers, wasting years of their professional direction.

How to transition quickly? Combining my years of work and experience in training newcomers, I have summarized a relatively effective method to cleverly integrate the basic C language knowledge required for product development into three examples, allowing newcomers to learn, encounter challenges, and think through these examples to continuously improve their C language skills and quickly gain the ability to participate in product development.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

How to Learn C Language?

Many newcomers like to ask a similar question: “How to learn… ?” Some friends might copy a bunch of book materials, and the more enthusiastic ones will guide which book to read first. But unfortunately, many times it just stops there; a pile of materials quietly lies on the computer hard drive, only to be opened and glanced at the table of contents by chance. Having walked through some companies, the training systems are generally like this:

1. After a newcomer joins, the mentor gives them a pile of materials to read, and then the newcomer reluctantly reads some of it; 2. One day when the mentor is not busy and remembers this newcomer, they hand them a product to tinker with; 3. Unfortunately, specific products generally involve multiple disciplines, and facing a pile of questions, the newcomer feels like they are walking on clouds and cannot move forward; 4. After a while, some people overcome the despair cliff at the beginning of their job, accumulate their knowledge, and slowly start to get in touch with the product, but due to the lack of various documentation, they can only learn and adjust at the same time; 5. Years later, newcomers become veterans, and a new product system is born; 6. Then the above dead cycle repeats.

Over time, the company’s product system becomes very chaotic, technology is difficult to reuse, and effective accumulation is impossible; the kind of teamwork that is like using one’s arms and fingers becomes empty talk. How can we break out of the above dead cycle? In my career, I have conducted a lot of exploration and experimentation, and I have come to realize: not only do we need to push the training of newcomers forward as much as possible and systematize it, but we also need to integrate it into our design and team philosophy as early as possible. Therefore, in the subsequent three C language examples, everyone will experience that many things are not just about C syntax.

As mobile internet has become popular, online education has also become trendy, and a large number of video tutorials have appeared online. Following the pulse of the times, I tried to turn the content that newcomers need to learn into books and videos, but the results were average. Later, I studied this field in detail and found that most videos merely transferred the content of university classrooms onto the internet; many people only endure watching a few episodes before it peters out, resulting in poor outcomes. I also have some thoughts and reflections on online education, which I will describe in a dedicated article later; for now, let’s not delve into it. In short, the initial attempts were not very successful.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language First Example: Unclear Requirements

The first example: “Write a console program that, given the heights of the inner and outer rhombuses, outputs a hollow rhombus.” Doesn’t that seem simple? As I write this, I hope you, the reader, can stop for a moment to think and then take action, writing a few lines of code for a small test before continuing to read; the rewards will be much greater. Over the years, I have trained many people, and most of them, upon seeing this question, immediately start writing the program, and I feel a bit disappointed inside. Why?

Generally, software personnel hired through layers of scrutiny by the company can usually get this example right. However, unfortunately, more than half of them cannot accurately implement it, just the understanding of the rhombus height varies greatly, leading to chaotic executions like these:

Or like this:

Or simply like this:

Why does this happen? This is precisely the first major pitfall of this example: unclear requirements. Encountering setbacks right from the start can be a harsh blow for many newcomers, but it also becomes a deeply memorable lesson. In team collaboration, there will be a lot of communication, and during the communication process, ambiguities will always arise to some extent, and precisely these ambiguities will lead to unclear requirements, causing a lot of rework and even project failures. My first piece of advice to newcomers is: express the requirements in your own words and confirm with the other party before implementation. This point is very important in future team collaborations, so I embedded this point early into the onboarding training.

After some back and forth, the first example is re-described as follows: “Write a console program where the user inputs the heights of the inner and outer rhombuses, and outputs a hollow rhombus. The height of the rhombus is defined as the height of the upper triangle of the rhombus. For example, if the input is 5 and 3, the output will be as follows: *

*** ** ** ** **** ** ** ** ** ** *** *

Here are a couple of additional points:

1. Defining the height of the rhombus may seem counterintuitive, but in the programming world, simplicity is the rule. If defined as the total height of the rhombus, it would not only require positive integer checks but also odd number checks, increasing the complexity of the program.

2. Describing with an example is much more effective than a pile of text; a picture is worth a thousand words. Once it’s clear what to do, many people go back, but in many cases, the execution results are as follows:

This leads us to the second important concept: boundary checking. Given two numbers, how to succinctly and completely check whether they are valid inputs is a fundamental skill in programming. This content is generally not emphasized in university teaching, but if we want to produce qualified embedded products, this must be given sufficient attention. How to elegantly perform boundary checking is left for the reader to ponder, and by the way, think back to my earlier definition of the height of the rhombus. After being sent back a second time, many people will feel impatient; the rhombus hasn’t been output yet, but a pile of rules is being crammed in. In such cases, I will explain to them the characteristics of embedded programs (which I will describe slowly in later texts) and share my previous experiences. When I first started working, due to CPU speed limitations, programs were mainly in assembly language. My boss asked me to write a small piece of assembly code, but every time I submitted it, I was scolded harshly and told to revise it until eventually, I couldn’t save a single assembly instruction, and only then did it pass. Looking back, for such a small example, I actually became familiar with most assembly instructions and understood my boss’s good intentions. Nowadays, newcomers are a bit more sensitive and don’t dare to scold directly, so we can only explain things slowly. After the second round of back and forth, most newcomers can adjust their attitudes a bit, and this time the example they write is basically in line with the requirements. However, when they excitedly come over to submit it, I instead don’t look at the program but start asking about the debugging skills used during the implementation of this program. This example is a bit challenging for newcomers; it’s impossible to write it perfectly in one go, and they will definitely experience some painful debugging processes. However, under the current university education system in China, basic debugging skills are not emphasized, and newcomers are expected to realize that debugging is a fundamental skill that needs to be given sufficient attention and continuously improved. The road is really tough, and now we are just starting out with a small example; for many, this is already a painful experience. To become a qualified embedded engineer, one needs methods, intelligence, and more importantly, the sweat and persistence behind it. This series of articles will lead everyone to experience my growth journey, but cannot replace your own efforts and sweat.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language Second Example: Boundary Checking

In the previous text, I mentioned that this example introduces three important concepts, the second of which is: boundary checking. Given two numbers, how to completely and succinctly check whether they are valid inputs is a fundamental skill in programming. Unfortunately, none of these examples have achieved that. Based on my years of experience in training, let me elaborate. Inputting two numbers, one for the height of the outer rhombus (denoted as m), and one for the height of the inner rhombus (denoted as n), there are several criteria:

1. Both heights should be positive integers (the inner rhombus height can be 0); 2. Due to the size limitation of the screen, the height of the rhombus should be limited; 3. The height of the outer rhombus should be greater than that of the inner rhombus.

In the past, when training people, I liked to input values like (1000, 800) first, because this is an easy-to-overlook area, and many people would leave feeling frustrated. Most people lack criteria, while some are more cautious by nature and tend to write a lot of judgment conditions, like this:

if (m > 0 && n > 0 && m > n && m < 30 && n < 30) { ……}

In summary, they fail to grasp the basic principle that judgment conditions should be complete yet concise. In fact, this example is simple; with a little analysis, the judgment conditions can be reduced to three, as follows:

1. The height of the inner rhombus is greater than or equal to 0; 2. The height of the outer rhombus is less than a given limit (let’s assume 30); 3. The height of the outer rhombus is greater than the height of the inner rhombus;

It can be written in the program as follows:

if (n >= 0 && m > n && m < 30) { ……}

Do you remember in the first section where we defined the height of the rhombus as the height of the upper triangle? The benefit of this is the simplification of the judgment; concepts serve the goal; otherwise, the judgment at this point would require additional odd-number checks, which would reduce simplicity. Haha, you can ponder this again. In embedded products, the robustness of the final product often manifests in these little simple judgments. This meticulousness is also hoped that newcomers can fully realize this as they gradually integrate into the R&D team.

Finally, we are about to start outputting the hollow rhombus. However, for freshly graduated university students, this example is still a bit convoluted. What is their logical thinking like?

Most people will find that outputting a hollow rhombus is slightly more complex. So, if we modify it to output a rhombus, does it become much simpler? If that still seems complex, how about modifying it to output a triangle? Haha, in short, it’s about simplifying complex problems step by step.

The initial problem becomes much simpler: given the height of the triangle m, output the triangle, for example, m=5, the output will be as follows:

*

***

*****

*******

*********

To make it intuitive, let’s show the spaces as well, for example:

—-*

—***

–*****

-*******

*********

At this point, the conclusion is already very visual; the spaces output on each line decrease from m-1, while the stars output increase from 1, and the loop is m. The program example is as follows:

for (i = 1; i <= m; i++){  j = m - i;  while (j--)    printf(" ");  j = i * 2 - 1;  while (j--)    printf("*");  printf("\n");}

When the problem complicates, considering a hollow triangle means there’s an additional triangle inside. When we output, we can simply deduct it; let’s assume n=3, the output will be as follows:

—-*

—***

–****

-**—**

**—–**

The program example is as follows:

for (i = 1; i <= m; i++){  j = m - i;  while (j--)    printf(" ");  j = i * 2 - 1;  for (k = 0; k < j; k++)  {    if (k < m-n || k >= j-m+n)      printf("*");    else      printf(" ");   }   printf("\n");}

Further complicating, to output a complete hollow rhombus, we only need to repeat the previous program, just reversing it and adjusting the height (subtracting 1), and I won’t show the example program here; those interested can try it themselves.

Some people, when implementing this program, start by analyzing the hollow rhombus directly. The most intuitive analysis strategy is to divide the rhombus into three layers: the upper triangle, the lower triangle, and the hollow part in the middle, as illustrated below:

*

***

** **

** **

** **

** **

** **

***

*

Following this thought process, the program example is as follows:

for(i=1;i<2*m;i++){    if(i<=m-n) {        star = 2*i-1;        empty= m-i;        while(empty--)            printf(" ");        while(star--)            printf("*");    }    else if(m-n<i &&  i< m+n && j< 2*n) {        if(j <= n && i <= m){            num_empty = 2*j-1;            empty = m -i;        }        else{            num_empty = 2*(2*n-1-(j-1))-1;            empty = i-m;        }        num_star = star = m-n;        while(empty--)            printf(" ");        while(star--)            printf("*");        while(num_empty--)            printf(" ");        while(num_star--)            printf("*");        j++;    } else {        star = 2*(2*m-1-(i-1))-1;        empty = (2*m-1-star)/2;        while(empty--)            printf(" ");        while(star--)            printf("*");    }    printf("\n");}

No matter what, this example is completed, but have you noticed that the above programs cannot be considered elegant? With various m and n expressions, after a while, it looks just as tangled as a mess. Imagine if this was a product program, how would future developers read and maintain it?

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language Third Example: The Simple Beauty of Numbers

Beauty always captivates the heart. When we think of beauty, we often think of pleasing paintings, melodious music, and exquisite poetry… However, mathematics, the queen of natural sciences, contains more beauty than poetry and painting. Elegant programs may indeed harbor the beauty of mathematics behind them. In some examples described in the previous section, we always struggled to piece together various m and n expressions. Instead of this arduous search, why not directly place this hollow rhombus on a coordinate axis? On the computer screen, people are accustomed to calling the right side the x-axis and the bottom the y-axis, drawing the hollow rhombus on the screen, as illustrated below:

Then we can use analytic geometry knowledge to outline the hollow rhombus, the program example is as follows:

for (x = 0; x < m * 2 - 1; x++){    for (y = 0; y < m * 2 - 1; y++)    {        if (abs(m - 1 - y) <= m - 1 - abs(m - 1 - x) &&            abs(m - 1 - y) > n - 1 - abs(m - 1 - x))            printf("*");        else            printf(" ");    }    printf("\n");}

We have concentrated all the judgments together, making it easy to understand what this large judgment statement is for when reading the program. Isn’t it much more elegant than previous implementations?

However, this criterion seems quite complex. Is there a better way? Many friends may have thought of this when they saw the illustration above, which is to move the coordinate axis to the center of the rhombus, as illustrated below:

The four edges of the outer rhombus can be described with expressions as follows:

(+x) + (+y) < m(-x) + (+y) < m(+x) + (-y) < m(-x) + (-y) < m

The combined expression is: abs(x)+abs(y)<m. At this point, the program example is as follows:

for (x = -m; x <= m; x++){    for (y = -m; y <= m; y++)    {        t = abs(x) + abs(y);        if (t >= n && t <= m)            printf("*");        else            printf(" ");    }    printf("\n");}

What does everyone feel about this piece of code? I remember when I discovered this implementation, I was first shocked by such simple mathematical beauty. If you feel the same way, I firmly believe you can walk far and high on the thorny path of programming…

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language Fifth Example: Huge Differences Between Embedded C and Desktop C

During my university years, I studied computer science, and my interest in programming was focused on various clever algorithms and implementations. After starting work, I began engaging in embedded product development, encountering numerous obstacles and hardships, only to realize that embedded C language and desktop C language are different. To help newcomers quickly step into the embedded threshold, today is destined to be a critique meeting; experiencing twists and turns is essential for growth. First, let’s take the most elegant implementation as a target, as shared in the last section:

for (x = -m; x <= m; x++){    for (y = -m; y <= m; y++)    {        if (abs(x) + abs(y) >= n && abs(x) + abs(y) <= m)            printf("*");        else            printf(" ");    }    printf("\n");}

This implementation places the most complex conditional checks within two layers of loops, thus the program efficiency is very low. In embedded programming, especially in some strongly real-time modules, performance is often a tightrope. Industrial embedded product development pursues teamwork, product maintainability, performance, and resource balance. Readability, maintainability, CPU computing power, memory, reliability, and other factors become resources that require our careful consideration. Often, we like to refer to programming for industrial embedded products as dancing on the tip of a needle. Aside from this, the previous program has many shortcomings, for example:

1. The entire program is often mixed together, increasing the complexity of reading the program for others; 2. Names like m and n are typical of school styles; 3. Various complex m and n expressions, after a while, would make anyone feel overwhelmed; …

Therefore, we need to continue on our path. For the convenience of subsequent program implementations, we will structure the entire program as follows:

int main(){    /* Input inner and outer rhombus heights and perform validity checks */    /* Loop to output rhombus */    for (……)    {        /* Output leading spaces */        /* Output left star */        /* Output middle spaces */        /* Output right star */        /* Output newline */        printf("\n");    }    return 0;}

Looking at this program framework, it becomes easy to understand that we require the output of the rhombus in a single-layer loop. Upon careful observation of the structure of the hollow rhombus, each line can be abstracted into four parts: leading spaces, left star, middle spaces, and right star. We only need to find the function relationships between these four values and the line number, and the entire program will be resolved smoothly, as illustrated below:

—-*—***–**-**-**—****—–**-**—**–**-**—***—-*

My professional mentor often discussed the concept of computer programming thinking with me. To this day, we have yet to provide an accurate definition for it, but through repeated iterative training, we have gradually understood what implementations can be called computer programming thinking. For example, in the world of computers, we often refer to the mouse, keyboard, disk, and data on the disk as files that can be traversed simply. Why do we abstract such disparate things into a unified concept? This cannot be explained in a single sentence; I will slowly guide everyone to experience this, but this design philosophy is ubiquitous in product development. After inserting these two paragraphs of trivialities, let’s return to our rhombus output example. Clearly, the structure of each line is not identical (recall that the second implementation version separately outputs by type), but we insist on treating them the same. Perhaps I hope to plant the seed of architectural design in the minds of newcomers from the very beginning, looking forward to its later germination. Although this topic starts with newcomer training, it is not without relevance to my own growth journey. The difference is that I grew through repeated bumps and falls. By integrating the stumbles I experienced into the examples of C language training for newcomers, and even writing out my entire growth history, I hope others can learn from my experience and grow more solidly and quickly.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language Sixth Example: Huge Differences Between Embedded C and Desktop C

In the previous section, we have established the basic framework of the program and briefly introduced the concept of programming thinking. It seems that newcomers can quickly write programs that meet requirements. Unfortunately, everyone’s growth path is not always smooth. I remember when I first learned computer programming, which skills were not gained through a lot of imitation, gradually integrating into my knowledge system, growing bit by bit. Based on the overall framework requirements from the previous section, many newcomers I have trained, including some fans who have emailed me, produced programs that are basically old wine in new bottles—just a rehash of previous example implementations. Let me show you a sample; feel free to reflect on whether you have the impulse to implement it this way.

/* Loop to output rhombus */for (……){    /* Output leading spaces */    if (upper rhombus)       output in triangular form;    else       output in inverted triangular form;    /* Output left star */    if (upper triangle)       ...    else if (center hollow)       ...    else       ...    /* Output middle spaces */    if (upper hollow)       output in triangular form;    else if (lower hollow)       output in inverted triangular form;    /* Output right star */    if (upper triangle)       ...    else if (center hollow)       ...    else       ...    /* Output newline */    printf("\n"); }

Note: This example combines the second implementation version, dividing a rhombus into upper triangle, lower triangle, and middle hollow parts for separate output. For details, please refer to “Onboarding C Language Example One (2).” How should it be implemented? In the previous section, we mentioned that the focus is on finding four function relationships. The expressive power of words is often weak, so let me show you a casual hand-drawn diagram I used when guiding newcomers; perhaps you will immediately understand.

Understanding the above diagram, I want to emphasize a few points:

1. Each line of the hollow rhombus must be treated equally; forget about dividing it into upper triangles, lower triangles, and middle hollows; 2. No if statements should be allowed within the four function relationships, but public functions like abs are permitted; 3. There are no shortcuts to success, and there are no shortcuts to learning skills; put aside the restless heart; as we reach this point, many people have written seven or eight versions already. Thanks to the current university education model, many people are becoming anxious again. Immediate reassurance is still necessary; a frequently joked line is to look at your senior xxx; don’t think that they are now handling several products with ease; they also wrote it over ten times back then, haha. In fact, many people are aware of my intention behind this. University learning is often superficial, and many things are eager for quick success. However, carrying this mindset into the workplace and product development can be fatal. I only want to use this method to help newcomers avoid detours. My good intentions often lead to countless misunderstandings; introverts may silently curse me, while the bold ones will directly question what significance there is in my torturing them. Haha. After this guidance, most people can smoothly complete this example according to the requirements, even though each part is a complex expression. At this point, I will explain a very crucial computing concept: iteration. The embedded products we engage in are strong real-time industrial products, often involving complex calculations like Fourier and require completion within designated time limits. Therefore, efficiency in calculations is highly demanded, and the most common strategy is to use iteration, retaining valuable intermediate calculation results to reduce overall computation. Through iteration, not only do the expressions for each part not become overly complex, but it also reduces the computational load. This example is relatively simple and doesn’t require much technical content, and most people quickly complete the iterative adjustments, but the contrast in results is quite impressive and can be considered an extra gain. Thus, all technical points of the hollow rhombus program have been described. Following these requirements, most people can write a program that meets the criteria. I highly recommend you, the reader, to write and adjust it personally; in the next section, I will post the standard answer, and you can compare it. The differences will be the starting point for our subsequent content.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

C Language Seventh Example: Continuously Perfecting the Answers in Your Mind

In the previous sections, I listed a very elegant implementation. Some friends reminded me that the output of that program was incorrect. I tested it and indeed found it to be so. When I wrote the program, I did so directly in document form, and some examples were copied from the programs written by the young people, focusing on the expression of intent; none of the program codes were tested, lacking rigor. Here’s the optimized version:

for (x = -m+1; x < m; x++){    for (y = -m+1; y < m; y++)    {        t = abs(x) + abs(y);        if (t >= n && t < m)            printf("*");        else            printf(" ");    }    printf("\n");}

Returning to the previous section, the newcomers stumbled and finally wrote a program that met the technical requirements, and everyone was happy, thinking they were done. Unfortunately, the road remains long. At this point, I will share the standard answer for this topic, allowing everyone to compare it with their own written programs. Previously, the programs were provided in fragmentary forms, while the standard answer is provided in a complete format, as illustrated below:

/********************************************************************* *   Copyright (C), 1999-2004, xxxxxx. Co., Ltd.* *   File Name: diamond.c*   Software Module: Hollow Rhombus Output*   Version Number: 1.0*   Generation Date: 2003/3/23*   Author: Xiao Ma Er*   Function: Hollow rhombus output, this program occupies little memory but has larger calculations, can be modified to "memory for resources" algorithm* *********************************************************************/#include <stdio.h>/* Maximum height of rhombus */#define MAX_DIAMOND_HEIGHT 16/* Pre-declaration */int myGreater(int n);/* Main program */int main(){    int n, nRow;    int nIn, nOut;    int nCount1, nCount2, nCount3;    /* Input inner and outer rhombus heights and perform validity checks */    for (;;)    {        printf("Input inner and outer rhombus heights (max %d rows): outer height, inner height:\n", MAX_DIAMOND_HEIGHT - 1);        scanf("%d,%d", &nOut, &nIn);        if (nIn < nOut && nOut < MAX_DIAMOND_HEIGHT && nIn >= 0)            break;        printf("Invalid input, please re-enter:\n\n");    }    /* Loop to output rhombus */    nIn = nOut - nIn;        /* Adjust to the difference between inner and outer rhombus */    for (nRow = -nOut+1; nRow < nOut; nRow++)    {        // Line number        printf("%-010d", nRow);        /* Output leading spaces */        nCount1 = nRow >= 0 ? nRow : -nRow;        /* Take absolute value */        for (n = 0; n < nCount1; n++)            printf(" ");        /* Output left star */        nCount1 = nOut - nCount1;            /* Outer triangle part, used for subsequent iterations */        nCount2 = myGreater(nCount1 - nIn);  /* Inner triangle part, used for subsequent iterations */        nCount3 = nCount1 - nCount2;         /* The difference between inner and outer is the actual output needed */        for (n = 0; n < nCount3; n++)            printf("*");        /* Output middle spaces */        nCount3 = 2 * nCount2 - 1;           /* Expand from triangle to rhombus */        for (n = 0; n < nCount3; n++)            printf(" ");        /* Output right star */        nCount1--;                           /* Outer triangle part */        nCount2 = myGreater(nCount1 - nIn);  /* Inner triangle part */        nCount3 = nCount1 - nCount2;         /* The difference between inner and outer is the actual output needed */        for (n = 0; n < nCount3; n++)            printf("*");        /* Output newline */        printf("\n");    }    return 0;}/* Get number greater than 0 */int myGreater(int n){    if (n < 0)        return 0;    return n;}

What do you feel when you see this standard answer? Can you find the differences between it and your own program? In the next section, we will use this program as a starting point to introduce some characteristics of real product code.

Dancing on the Tip of Embedded C Programming: 7 Examples from a 15-Year Veteran

Set Aside Grand Theories and Discuss Embedded Code

In the previous section, we provided the standard answer written in the style of real product code, hoping that our partners can find valuable points in it. 1. Meaningful Variable Naming When university teachers taught programming, they focused primarily on syntax, showcasing all the syntax to everyone (I strongly disagree with this learning method; I will later showcase my approach, known as the “big tree principle” within the project team). Therefore, they often used short programs to demonstrate syntax, but because the programs were short, variable naming was somewhat casual. Thus, i, j, k, m, and n became frequent visitors, and inadvertently carried into the product, and then… But in product development, even relatively simple devices have a large amount of code; for the sake of code readability and maintainability, meaningful names become very important. In the standard example, the use of nIn and nOut is intended to express the heights of the inner and outer rhombuses in simple English words. Some friends may have read books like “The Art of Readable Code” or “Clean Code”; the authors emphasize using accurate English words to express specific meanings. However, we are Chinese, and it is already quite challenging to think of some simple vocabulary, let alone express accurately, so after countless iterations and explorations, our project team has formed a habit of variable definition: use simple similar English vocabulary as much as possible, and all global variables must have accurate Chinese comments. For some automatic variables within functions, due to their small scope, sometimes comments can be omitted. There are many stories about variable naming; this is just a start, and we will have a dedicated series of articles to introduce this. Remember, in the code of real products, meaningful variable naming is essential; forget about m and n.

2. Code Sections What is a section? The first time I learned this concept was during the global millennium, when I was still in my senior year. I worked part-time at a company in Beijing, which had undertaken a project for a Japanese bank. The Japanese side had strict requirements for code quality and specifically sent an expert to explain various requirements and the reasoning behind them. At that time, I was still young and quite rebellious, so most of the expert’s kind advice went in one ear and out the other. However, I deeply remembered the concept of sections (perhaps this was discussed from the beginning, haha; I lost patience for the following content, and the reading ratio of this series of articles gradually declined, which I guess is a similar reason). When we read code, human thinking stays focused on a narrow point for a period of time. If faced with endless code, we subconsciously perceive it as a long and smelly cloth, evoking a rebellious sentiment. Therefore, we need to logically divide the code into chunks, using spaces as separators, and then add appropriate comments before each chunk to explain the function of that code. After such modifications, the experience of reading code will be completely different. The value of sections is far beyond this; we will slowly discuss it in the series of articles on programming specifications. At this point, we only require our partners to understand that piling up long and stinky code is incorrect. In fact, in the fifth section, when I agreed on the overall structure of the program, I already had this intention; everyone can recall and reflect on it.

/* Loop to output rhombus */for (……){    /* Output leading spaces */    /* Output left star */    /* Output middle spaces */    /* Output right star */    /* Output newline */ }

3. Detailed Annotations Some codes possess a certain depth, and after a while, they may be forgotten. By using simple annotations on the right side, not only does it facilitate subsequent code reading and understanding, but the annotation points are generally key code segments, which also ease code reviews later on. In the example code, you will see simple annotations on the right side of iterative expressions (due to the formatting issues of WeChat public account, they often end up on the next line), which mainly serves this purpose. However, everything should be done in moderation; many newcomers tend to add a lot of annotations on the right side. It requires long-term training and gradual insight to pick out valuable annotations; perhaps one day, when you look back at your code, you will delete many useless comments, indicating that you have mastered this skill.

4. Resources In embedded systems, resources are something that needs to be monitored at all times. What are resources? In our concept, not only the size of memory and flash space is a resource, but also CPU computing power is a resource, and even code readability (maintainability), code implementation complexity (the human cost incurred), reuse rate, and many other aspects are all referred to as resources. Good steel must be used on the cutting edge, but first, we must understand where the cutting edge is. Lacking clear boundaries, talking about improving resource utilization is meaningless. For instance, readability is paramount; if memory and CPU resources are relatively abundant, our most elegant implementation version is the best. If CPU computing power is tight, the standard implementation from the previous section is better. If memory is slightly abundant, to enhance code readability, we have even better methods. Some people have already mentioned this method to me; I wonder if everyone has felt that by now, simply outputting a rhombus is such an easy task, why bother complicating it with a hollow rhombus that makes the program messy? But if we use an array to represent the entire rhombus output, first output a rhombus with stars, then output a rhombus with spaces, and then output the entire array, wouldn’t that be very simple? The readability of the code would instantly skyrocket, and the execution efficiency would also improve, occupying just a bit more memory.

Summary

After a marathon journey, we finally reach the last chapter, where we summarize the knowledge points mentioned in the first hollow rhombus output example:

1. Clear understanding of requirements; the most used strategy is: express requirements in your own words and confirm with the other party before implementation. 2. Boundary checking; make newcomers aware that in embedded products, the robustness of the final product often manifests in these little simple judgments. 3. Training in basic debugging skills; as the saying goes, “A craftsman must sharpen his tools before doing his work;” there’s no need to elaborate. 4. Introducing programming thinking; slowly understanding the value of abstraction is a challenge, but to go far, one must endure it. 5. In numerical calculations, a very important concept: cleverly using iteration. 6. Introducing basic programming specifications; understanding the concept of sections and recognizing the importance of product code readability and maintainability. 7. In the field of embedded programming, resources are limited, and we must learn to dance on the tip of a needle. 8. Writing work notes; being good at summarizing and forming the habit of reflecting on growth.

Remember, when I first engaged in embedded programming, my professional mentor showed me his notebook filled with various debugging records, insights, technical materials, and knowledge summaries. I finally understood why he gained recognition from everyone in the company. Therefore, our team formed an unwritten rule: everyone must keep work notes, regardless of the method or format, as long as they start recording.

Leave a Comment