Today, I will share several practical tips for embedded C programming, hoping they will be helpful to everyone.
1. Dynamic Binding and Callback Functions
Callback functions can achieve dynamic binding, which can reduce coupling between layers to some extent. Many beginners may not yet understand callback functions, but they can be understood with the help of the diagram below:

Generally, the order of function calls is that the upper layer function (caller) calls the lower layer function (callee). However, as we can see from the diagram, the function 2 of the lower module calls the function 3 of the upper module, which is the opposite of the usual calling process. This process is called a callback, where the function 3 of the upper module is the callback function. The representation of a callback function is a function pointer.
The C library stdlib.h
includes a sorting function: qsort
. The prototype of this sorting function is:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
Parameters:
- base — A pointer to the first element of the array to be sorted.
- nitems — The number of elements in the array pointed to by base.
- size — The size of each element in the array, in bytes.
- compar — A function used to compare two elements, i.e., a function pointer (callback function).
int compar(const void *p1, const void *p2);
If compar
returns a value less than 0 (< 0), then the element pointed to by p1
will be placed before the element pointed to by p2
;
If compar
returns a value equal to 0 (= 0), then the order of the elements pointed to by p1
and p2
is undefined;
If compar
returns a value greater than 0 (> 0), then the element pointed to by p1
will be placed after the element pointed to by p2
.
Example:

#include <stdio.h>
#include <stdlib.h>
int compar_int(const void *p1, const void *p2)
{
return (*((int*)p1) - *((int*)p2));
}
void test_qsort(void)
{
int arr[5] = {8, 5, 10, 1, 100};
printf("Before sorting:");
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
qsort((int*)arr, 5, 4, compar_int);
printf("\nAfter sorting:");
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
}
int main(void)
{
test_qsort();
return 0;
}
Compilation and Run Result:

This concludes today’s sharing. If there are any errors, please feel free to point them out. Thank you. This is the first installment, and I will continue to share more practical programming tips and experiences in future posts. Please stay tuned. This article only summarizes some practical tips and does not imply that they should be used in every scenario; each problem should be analyzed specifically.
2. Using Macros for Struct Initialization
If a struct is used frequently, using macros to assign values to the struct is a very convenient approach.
Example:
#include <stdio.h>
#define NEW_RECT(length, width) {(length), (width)}
typedef struct _Rect
{
int length;
int width;
}Rect;
int main(void)
{
Rect rect = NEW_RECT(10, 5);
printf("rect length = %d, width = %d\n", rect.length, rect.width);
return 0;
}
Compilation and Run Result:

This method is also seen in the low-level GPIO driver of RT-Thread:

3. Struct with Built-in Function Pointers
We often construct structs to store data and then use these structs in some functions. Next time, consider binding the data with the functions that operate on the data for clearer understanding.
Example:
#include <stdio.h>
#define NEW_RECT(length, width) {(calc_area), (length), (width)}
typedef struct _Rect
{
int (*calc_area)(struct _Rect *pThis);
int length;
int width;
}Rect;
int calc_area(struct _Rect *pThis)
{
return (pThis->length * pThis->width);
}
int main(void)
{
Rect rect = NEW_RECT(10, 5);
printf("rect length = %d, width = %d\n", rect.length, rect.width);
printf("rect area = %d\n", rect.calc_area(&rect));
return 0;
}
Compilation and Run Result:

4. Using do{}while(0) to Encapsulate Macros
#define DBG_PRINTF(fmt, args...) \
do\
{\
printf("<<File:%s Line:%d Function:%s>> ", __FILE__, __LINE__, __FUNCTION__);\
printf(fmt, ##args);\
}while(0)
5. void*
I have previously introduced void*
. We often encounter void*
in functions, such as:
void *malloc(size_t size);
void *memcpy(void *destin, void *source, unsigned n);
......
void *
is often used for function encapsulation, but it can also be used in other places, such as defining a void*
type private pointer in a struct for easier struct extension. When encapsulating our own functions, we can also consider whether it is necessary to use void*
to enhance the generality of the function.
If this article has been helpful to you, feel free to share it. Thank you!
END
Source: Embedded Miscellaneous
Copyright belongs to the original author. If there is any infringement, please contact for deletion.
▍Recommended Reading
C Language, Circular Queue
Embedded Development, Should You Take Side Jobs?
ARM Processor Bootloader Low-level Process
→ Follow to avoid getting lost ←