Five Practical Tips for Embedded C Programming

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:

Five Practical Tips for Embedded C Programming

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:

Five Practical Tips for Embedded C Programming
#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:

Five Practical Tips for Embedded C Programming

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:

Five Practical Tips for Embedded C Programming

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

Five Practical Tips for Embedded C Programming

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:

Five Practical Tips for Embedded C Programming

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!

Five Practical Tips for Embedded C Programming

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 ←

Leave a Comment