Common Misuses and Knowledge of C Language in Microcontrollers

Common Misuses and Knowledge of C Language in Microcontrollers

When learning about microcontrollers, one truly understands what the C language is and what it is used for. However, the application of C language in embedded systems is just a small part of its overall usage, and there are many other applications as well. We won’t discuss those here. Are we not making many mistakes while writing programs? Even if the compilation passes, it may not yield the expected results, and it can be quite difficult to find where the error lies. I believe that a language, to be called a language, must be a tool for mutual communication and conveying intentions. As a language, it must have its own syntax, and to communicate effectively, one must first learn its syntax (such as expressions, functions, loops, pointers). I refer to this as the syntax of the C language. Although C language is powerful, it also has many pitfalls. Therefore, I have two purposes in posting this: first, to summarize some common misuses and errors in C language, and second, to summarize some basic syntax of C language.

First:

1. About Increment and Decrement (i.e., ++i, i++)

To increment or decrement a number, we can:

i += 1;

j -= 1;

However, C language also allows the use of ++ and — operators, which can be misleading because they can be used as both prefix and postfix. This can change the value of the operand. Let’s take a look:

i = 1;

printf(“i is %d\n”, ++i); /* prints i is 2 */

printf(“i is %d\n”, i); /* prints i is 2 */

The result of the expression i++ is i, but it will cause i to increment afterwards:

i = 1;

printf(“i is %d\n”, i++); /* prints i is 1 */

printf(“i is %d\n”, i); /* prints i is 2 */

The first printf shows the original value of i before incrementing, while the second printf shows the new value of i after it has changed; of course, I won’t provide examples for — as it is similar.

However, using ++ and — multiple times in the same expression can often be difficult to understand. Let’s look at the following example:

i = 1;

j = 2;

k = ++i + j++;

The final values of i, j, and k are 2, 3, and 4 respectively, where ++i is 2 and j++ is 2.

In summary: whether it is ++i or i++, after executing this statement, the value of i is incremented; only the value of (++i) is incremented, while (i++) remains unchanged.

2. typedef vs #define

2.1. typedef

In C language, besides directly using standard type names (such as int, char, float, double) and user-defined structures, unions, pointers, and enumerated types, one can also use typedef to declare new type names to replace existing type names.

typedef unsigned char u8;

typedef unsigned int u16;

u8 count;

u16 time;

typedef struct

{

u8 month;

u8 day;

u16 year;

} DATE;

DATE birthday;

To summarize, the method for declaring a new type name is as follows:

1. First, write the definition body as if defining a variable (e.g., unsigned int i)

2. Replace the variable name with the new variable name (e.g., replace i with u16)

3. Add typedef at the front (typedef unsigned int u16)

4. Then use the new type name to define variables

2.2. #define

2.1.1. Macro definition without parameters

#define identifier string

#define PI 3.1415926

Note:

1. Its function is to replace 3.1415926 with the specified identifier PI in this program.

2. Macro definitions use macros to replace strings, which means simple substitution without correctness checks. If written as

#define PI 3.l4l6926

where 1 is written as the letter l, the preprocessor will still substitute it without any syntax checks!

2.1.2. Macro definition with parameters

#define macro_name(parameter) string

#define S(a,b) a*b

area = S(a,b);

#define MAX(x,y) (x)>(y) ? (x):(y)

3. Differences between typedef and #define

Generally, typedef is preferred because it can correctly handle pointer types.

typedef char *String1;

#define String2 char *

String1 s1,s2;

String2 s3,s4;

s1, s2, and s3 are defined as char*, but s4 is defined as a char type.

3. Static Variables

Static variables can be roughly divided into three usages:

1. Used in local variables, becoming static local variables. Static local variables have two uses: memory function and global lifetime.

2. Used in global variables, mainly to restrict this global variable from being called by other files.

3. Used in class members, indicating that this member belongs to the class but not to any specific object within the class.

1. Static Local Variables

Static local variables belong to static storage, and they have the following characteristics:

(1) Static local variables are defined within a function, and their lifetime is the entire source program, but their scope remains the same as automatic variables, meaning they can only be used within the function that defines them. After exiting the function, although the variable still exists, it cannot be used.

(2) It is allowed to assign initial values to static local variables, such as arrays. If not assigned an initial value, the system automatically assigns a value of 0.

(3) For basic type static local variables, if not assigned an initial value during declaration, the system automatically assigns a value of 0. However, for automatic variables, if not assigned an initial value, their value is indeterminate. Based on the characteristics of static local variables, it can be seen that they are variables with a lifetime of the entire source program. Although they cannot be used after leaving the function that defines them, they can be used again when the function is called again, and they retain the value left after the previous call. Therefore, when a function is called multiple times and it is required to retain certain variable values between calls, static local variables can be considered. Although global variables can also achieve the same purpose, they can sometimes cause unintended side effects, so it is preferable to use static local variables.

For example:

void fun()

{

static int a = 1;

a++;

}

When entering this function for the first time, the variable a is initialized to 1 and then incremented by 1. Each subsequent entry into the function will not reinitialize a, but will only perform the increment operation. Before the invention of static, to achieve the same functionality, one could only use a global variable:

int a = 1;

void fun()

{

a++;

}

2. Static Global Variables

Static global variables are formed by adding static to global variables (external variables). Global variables themselves are static storage, and static global variables are also static storage. There is no difference in storage method between the two. The difference lies in that the scope of non-static global variables is the entire source program. When a source program consists of multiple source files, non-static global variables are valid in all source files. In contrast, static global variables limit their scope, meaning they are only valid within the source file where the variable is defined. In other source files of the same source program, they cannot be used. Since the scope of static global variables is limited to one source file, they can only be shared by functions within that source file, thus avoiding errors in other source files. From the above analysis, it can be seen that changing a local variable to a static variable changes its storage method, i.e., its lifetime. Changing a global variable to a static variable changes its scope, limiting its range of use. Therefore, the static specifier serves different purposes in different contexts.

3. Static Member Variables in Classes

The static keyword has two meanings; you can determine its meaning based on the context:

1. It indicates that the variable is a static storage variable, meaning the variable is stored in the static storage area.

2. It indicates that the variable has internal linkage (this situation refers to the variable not being within any {} as a global variable, in which case adding static makes it so), meaning that in other .cpp files, this variable is not visible (you cannot use it).

2. Static Functions – Internal Functions and External Functions

When a source program consists of multiple source files, C language divides functions into internal functions and external functions based on whether they can be called by functions in other source files.

1. Internal Functions (also known as Static Functions)

If a function defined in one source file can only be called by functions within that file and cannot be called by functions in other files of the same program, this function is called an internal function.

To define an internal function, simply add the “static” keyword before the function type, as shown below:

static function_type function_name(function_parameter_list)

{……}

The keyword “static” translates to “静态的” in Chinese, so internal functions are also called static functions. However, here, the meaning of “static” does not refer to storage method but rather to the scope of the function being limited to this file.

The benefit of using internal functions is that when different people write different functions, they do not have to worry about whether their defined functions will conflict with functions in other files, as naming conflicts do not matter.

2. External Functions

The definition of an external function: If a function is defined without the “static” keyword or with the “extern” keyword, it indicates that this function is an external function:

[extern] function_type function_name(parameter_list)

{……}

When calling an external function, it needs to be declared:

[extern] function_type function_name(parameter_type_list)[, function_name2(parameter_type_list2)……];

Common Misuses and Knowledge of C Language in Microcontrollers

Common Misuses and Knowledge of C Language in Microcontrollers

Some screenshots from electronic books

Common Misuses and Knowledge of C Language in Microcontrollers

【Complete Set of Hardware Learning Materials】

Common Misuses and Knowledge of C Language in Microcontrollers

Leave a Comment