Overview of Embedded C Language Code Style

Follow + star our public account, don’t miss the wonderful content

Overview of Embedded C Language Code Style

Author: HywelStar

Hi, it is often seen in job requirements that code should have a good style and be friendly. What constitutes a good style? For friends working in some large companies, there are certain coding style guidelines, such as those from Huawei, among many others. I won’t discuss Huawei’s coding style here. The coding style is usually formed gradually during the coding process, but if you don’t have a set of such guidelines, you can refer to this chapter. However, it should be noted that coding styles are not fixed, and there may be some differences among teams, so choose according to your own needs.

1. Code Formatting Tools

To have a good coding style, there must be certain constraints on formatting. Here, we simply check the code format. During coding, there will always be some formatting issues, such as how many spaces to use, whether there are spaces between symbols and variables. These minor issues are often overlooked while coding, which is when you need to use the IDE’s automated formatting tools to help you format your code with one click.

2. Automatically Add Comments to Files or Functions

Regarding this issue, when writing code, you will find that a .c file always has a header description. When you have hundreds or even thousands of files, it is certainly not feasible to copy and paste descriptions one by one, and these often require a certain format. When you make changes, you may need to modify the description. Here, it is strongly recommended to use the IDE’s automatic file description feature, which not only speeds up efficiency but also unifies the format, improving the overall quality of the code.

For how to add comments to files or functions, you can refer to the article [Link: Generate File Headers and Function Comments for Code].

3. Code Style

Software developers cannot escape the step of writing code. Since code is in English, it is recommended to use English for function names and variables.

3.1 Naming Conventions

Whether it is variable naming, function naming, or other related naming, do not mix Chinese and English! Keep it in English. It is not suitable to use names that overlap with keywords and library functions; choose camel case or underscore naming conventions for variable and function names based on your situation.

3.1.1 Variable Naming

Regarding variable naming, variable names must not use keywords, which everyone should already know;

Variable names should be as descriptive as possible. Avoid using overly non-general abbreviations or meaningless characters; try to use meaningful words to make the code easier to understand.

int return = 0;   // bad
int a, b;    // bad
float cnt, x1z;    // bad
int age, item_count;    // good
float temperature, height_in_meters;    // good

3.1.2 Function Naming

Function naming must not use keywords for naming. What does this mean? For example, <span>interruput</span>, <span>exit</span>, <span>public</span>, etc. In other words, you cannot use system-reserved keywords for naming.

Function names can use <span>camel case</span>, for example, <span>GetDevName</span>, or use <span>lowercase with underscores</span>, for example, <span>get_dev_name</span>, but these two styles should not be mixed. Maintain a consistent style (though there may be some differences in practice, such as when using a library with a different style than your original code, in which case you can choose to keep your own style).

Function names should clearly express the function’s purpose, avoiding ambiguous abbreviations or overly long names. It is recommended to use a verb plus a noun format.

Avoid conflicts with global names by considering associating function names with the project or module, adding a prefix to the function name.

# 1. Do not use keywords or system function interfaces
void exit(); // bad : conflicts with standard library function
void interrupt(); // bad : can cause misunderstanding
# 2. Function names should clearly express their purpose
void gdn();  // bad : unclear abbreviation
void calc();  // bad : unclear abbreviation
void get_the_name_of_the_current_active_device_in_the_network(); // bad : too long
void get_device_name(); // good : clearly expresses function
void calculate_sum();  // good : clearly expresses function
# 3. To avoid conflicts, consider adding prefixes to module or file names
// For example, if the current file is the network module, consider adding a prefix
void network_send_packet(); // function name with module prefix
// For example, if the current file is dev_config.c, consider adding a prefix
int dev_config_init();
int dev_config_save();

3.3.3 File Naming

File names should be descriptive and clear, avoiding vague names or abbreviations.

File names should not be too long and should not contain unnecessary information. If the file name is too long, it not only becomes difficult to manage but also increases the burden of writing and referencing.

File naming should be consistent. You can use several common naming styles, but mixing them is not recommended.

File names should avoid overly general terms to prevent confusion. Especially when the project becomes large, general file names can lead to file conflicts or misunderstandings about the file’s content.

# 1. File naming should clearly express content and functionality
a.c  // bad : lacks descriptiveness, cannot tell file content from name
temp.c  // bad : too vague, does not specify file's specific function
device_driver.c  // good : specifically describes file's function
network_utilities.c  // good : indicates it is a network-related utility function
# 2. File names should not be too long
device_driver_for_usb_network_interface_card_version.c  // bad : file name too long
usb_network_driver.c  // good : keeps it concise while clearly expressing functionality
# 3. Consistent style
network_utilities.c, device_driver.c // good : consistent lowercase with underscores
DeviceDriver.c, NetworkUtilities.c // good : camel case
# 4. Avoid using overly general names (for large projects)
util.c  // bad : file name too general, content unclear
test.c  // bad : unclear specific test content
memory_utilities.c  // good : clearly indicates it is a memory-related utility function
network_test.c  // good : indicates it is for network functionality testing

3.2 Macro Definitions

Regarding the naming of macro definitions, first use uppercase, and then express the meaning as clearly as possible.

Macro definition parameters should be protected with <span>()</span>, and the results should also be protected with <span>()</span>.

Do not use indentation in macro definitions; it is recommended to use <span>do...while(0)</span> to enclose macro code; however, it is advised not to use macros for overly complex functions.

#define SQUARE(x)         ((x) * (x))    // good
#define square(x)           ((x) * (x))    // bad
#define MIN(x, y)           ((x) < (y) ? (x) : (y))    // good
#define MIN(x, y)           x < y ? x : y    // bad
/* good */
#if defined(XYZ)
#if defined(ABC)/* do when ABC defined */
#endif /* defined(ABC) */
#else /* defined(XYZ) */
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */
/* bad */
#if defined(XYZ)    
#if defined(ABC)        /* do when ABC defined */    
#endif /* defined(ABC) */
#else /* defined(XYZ) */    
/* Do when XYZ not defined */
#endif /* !defined(XYZ) */

3.4 Structures, Enums, Typedefs

  • Structure or enum names must be lowercase, with optional underscore characters between words<span>_</span>
  • Structures or enums may contain keywords<span>typedef</span>
  • All structure members must be lowercase
  • All enum members should be uppercase
# struct only use name declaration, do not include _t
struct struct_name {    char* a;    char b;};
# When only using typedef to declare a structure, it must include a suffix, _t
typedef struct {    char* a;    char b;} struct_name_t;
# When using name and typedef to declare a structure, it cannot include the basic name, and it must include a suffix for the typedef part. _t_t
typedef struct struct_name {    /* No _t */    char* a;    char b;    char c;} struct_name_t;    /* _t */
# Enum
enum color_type {     COLOR_RED,         COLOR_GREEN,    COLOR_BLUE};

3.5 Error Handling

How to handle function return values and error codes, common error handling strategies such as <span>assert</span> and error logging.

In C language, functions need to return success or failure, but some established error codes should not be misused. Returning <span>0</span> indicates success!

Common error code conventions:

  • <span>0</span>: success (<span>SUCCESS</span>)
  • <span>1</span>: general error (<span>GENERAL_ERROR</span>)
  • <span>-1</span>: system-level error (<span>SYS_ERROR</span>)
  • <span>-2</span>: other errors

Some error codes may require additional definitions, which can be done using enums:

enum error_code {    SUCCESS = 0,    ERR_FILE_NOT_FOUND = 1,    ERR_INVALID_ARGUMENT = 2,    ERR_OUT_OF_MEMORY = 3};

Regarding error strategies, sometimes using <span>assert</span> can trigger program exceptions, causing the program to stop. This is usually used during debugging, and it is recommended to disable it in production environments.

#include <assert.h>
void divide_by_value(int divisor) {    assert(divisor != 0);  // ensure divisor is not 0    int result = 10 / divisor;    printf("Result: %d\n", result);}

Another strategy is regarding the error level of logs, which will not be elaborated here.

3.6 Others

Regarding other issues that may arise in code, most can be considered in terms of coding habits, and it is advisable to adopt standardization to reduce exceptions:

# 1. It is recommended to use explicit comparisons, not implicit comparisons
if (ptr == NULL || ptr != NULL) {    // good 
}
if (ptr || !ptr) {    // bad
}
# 2. Always use length or size variables
size_t/* OK example */void send_data(const void* data, size_t len) { /* OK */    /* Do not cast `void *` or `const void *` */    const uint8_t* d = data;/* Function handles proper type for internal usage */}
void send_data(const void* data, int len) {    /* Wrong, do not use int */}
# 3. Follow the original appearance of the code, for example, when using a library, there is no need to deliberately modify
# 4. Fragmented size_t length = 5;  /* Counter variable */uint8_t is_ok = 0;  /* Boolean-treated variable */if (length)         /* Wrong, length is not treated as boolean */if (length > 0)     /* OK, length is treated as counter variable containing multiple values, not only 0 or 1 */if (length == 0)    /* OK, length is treated as counter variable containing multiple values, not only 0 or 1 */
if (is_ok)          /* OK, variable is treated as boolean */if (!is_ok)         /* OK, -||- */if (is_ok == 1)     /* Wrong, never compare boolean variable against 1! */if (is_ok == 0)     /* Wrong, use ! for negative check */

4. Summary

Regarding code style issues, for formatting, you can use formatting tools to solve questions such as how many blank lines to leave, whether there are spaces between operators and numbers, and whether to wrap lines. For file or function comments, you can also use tools to automatically generate a general comment with a unified format. The author believes that there may be significant differences in naming conventions for writing code, file naming, and function naming, but observing more will help. For other specifications, you can follow the public account “Code Thinking” and send a private message to get the relevant document link..

Previous Recommendations

Priority Inversion in Embedded Systems and Its Solutions

Compilation Knowledge of Embedded Software

Common Techniques in Embedded Development

Generate File Headers and Function Comments for Code

How Much Do You Know About Loading Linux Kernel Modules?

How to Create Linux Device Files?

Public Account Directory Guide

Overview of Embedded C Language Code Style Click “Read the original text” to recharge together!

Leave a Comment