How to Use Complex Pointers in Embedded Programming

How to Use Complex Pointers in Embedded Programming

1. Introduction

In C programming, pointers are one of the most error-prone areas, especially when multiple pointers are involved, which can be quite confusing. This article analyzes the commonly used complex pointers in embedded systems to clarify the common pitfalls in using pointers in C language.

2. Function Pointers and Pointer Functions

In C language, functions have their addresses. Similarly, if we assign the address of a function to a function pointer, we can indirectly call the function through the function pointer.

The definition of a function pointer is as follows:

data_type (*fun)(parameter_list);

Since () has a higher precedence than *.

The definition of a pointer function is as follows:

data_type * fun(parameter_list);

Its return value is data_type *.

Example: Calling a function through a function pointer

Step 1: Define a function pointer

int* (*pfun)(int*,int*);

This defines a function pointer with a return type of int *, where the two parameters are two int*.

Step 2: Define a pointer function

int* fun(int*, int*);

Here, the return value of the function is int *.

Step 3: Implement the function pointer

int* fun(int* a, int* b)
{
    int* ret = 0;
    (*ret) = (*a) + (*b);
    return ret;
}

Step 4: Assign the function’s address to the function pointer

int main(int argc, char** argv)
{
    int a = 3, b = 2, c = 0;
    pfun = fun;
    c = *((*pfun)(&a, &b));
    rt_kprintf("c is %d\n", c);
    return 0;
}

The key part is the assignment and calling; the assignment uses pfun = fun;, while the indirect call uses *((*pfun)(&a, &b));.

3. Issues with Const-modified Pointers

First, let’s look at the following statements:

const int *p;
int const *q;
int *const r;
const int * const x;

In C programming, const is often used to modify a variable to prevent it from being changed.

The first two declarations const int *p; and int const *q; mean the same thing; both p and q are declared as pointers to const int. This means that in the program, the values of *p and *q cannot be modified. For readability, it is common to place const before the type.

int a = 3, b = 2;
const int *p = &a;
p = &b;
*p = 5; // error
rt_kprintf("*p is %d\n", *p);

Here, the value of *p cannot be modified, but the value of p can be changed.

For int *const r;

int *const r = &a;
r = &b; // error
*r = 6;
rt_kprintf("r is %d\n", *r);

Here, r = &b; is an error.

Combining the above operations, we get const int * const x = &a;. This needs to be assigned at the time of use and cannot be modified, meaning

x = &b; // error
*x = 6; // error

These operations are all incorrect.

4. Direct Jumping with Function Pointers

In real project development, we may need to jump directly to a specific address of a function using a pointer.

void (*function_p)(void);  // Define function pointer function_p, no return value, no parameters
function_p = my_func;  // Function pointer points to my_func function
(*function_p)();  // Run the function using the function pointer

This is equivalent to directly calling the my_func function. But what is the significance of this?

This raises a thought: we can jump to a function based on its address. For example, in a bootloader, after loading a binary file into memory, how do we execute this kernel program? This is essentially implementing a jump from the bootloader to the kernel.

((void(*)())0x80000)();

This means that the address 0x80000 is of function type and has no return value. When our kernel address is 0x80000, the program jumps there and does not return. This is a classic example.

5. Callback Functions

Callback functions can be considered an advanced application of function pointers in C. In simple terms, a callback function is a function that is called through a function pointer. This means we pass the pointer of a function as a parameter to another function, and we can consider the called function as a callback function.

Let’s analyze a specific example in the rt-thread to explore the usefulness of callback functions.

Those who have used the rt-thread operating system know that it adopts a device driver framework, allowing operations on driver devices using a virtual file system during development. Let’s take a look at the contents of the rt_device structure.

/**
 * Device structure
 */
struct rt_device
{
    struct rt_object          parent;/**< inherit from rt_object */

    enum rt_device_class_type type;                     /**< device type */
    rt_uint16_t               flag;                     /**< device flag */
    rt_uint16_t               open_flag;                /**< device open flag */

    rt_uint8_t                ref_count;                /**< reference count */
    rt_uint8_t                device_id;                /**< 0 - 255 */

    /* device call back */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

#ifdef RT_USING_DEVICE_OPS
    const struct rt_device_ops *ops;
#else
    /* common device interface */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)  (rt_device_t dev);
    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);
#endif

#if defined(RT_USING_POSIX)
    const struct dfs_file_ops *fops;
    struct rt_wqueue wait_queue;
#endif

    void                     *user_data;                /**< device private data */
};

We will focus on the callback function interfaces below.

rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

The first function indicates that when the underlying device receives data, this callback function can be called, and the upper application implements this interface.

The second interface is also an example of the underlying interface calling the upper application layer interface.

According to the rt-thread device programming model

Step 1: Find the device

rt_device_find

This returns a rt_device_t type device handle.

Step 2: Implement the rx_indicate function

xxx_dev->rx_indicate = xxx_rx_indicate;

Here, xxx_rx_indicate is the function we need to implement, which can release a semaphore to inform other threads that a message has arrived.

Step 3: Underlying call interface

dev->rx_indicate(dev, size);

When a message arrives, this interface is called. If the upper application has implemented this interface, that function will be executed; if not, it can check if dev->rx_indicate is null and not execute.

This way, the program achieves a reduction in coupling. If we directly call functions, the coupling in program design would be too strong. This is also a classic example of how rt-thread uses callback functions to reduce coupling.

6. Conclusion

Understanding pointer usage is crucial for C programming. Sharpening the axe does not delay the work of cutting wood; only by building a solid foundation can the upper structure be stable. Continuous accumulation and summarization of the basics will elevate one’s thinking. Programming design is not just about verbal skills, nor can it be mastered in a few months of quick learning; it requires daily practice. Without accumulating small steps, one cannot reach a thousand miles; without gathering small streams, one cannot form a river or sea. This serves as a self-encouragement.

How to Use Complex Pointers in Embedded Programming

1.Artificial Intelligence: Opportunities and Challenges in Embedded Technology

2.Amazing! Building a Retro Classic Computer with 6 Chips

3.The Complexity of Domestic Operating Systems

4.What You Must Know About Domestic CPUs for Embedded Systems, There Are Opportunities!

5.Can Linux Run on 8-bit MCUs?

6. Sometimes, rt-thread Needs to Be Trimmed to Fit Your Project!How to Use Complex Pointers in Embedded Programming

Disclaimer: This article is a network reprint, and the copyright belongs to the original author. If there are any copyright issues, please contact us, and we will confirm the copyright based on the materials you provide and pay for the manuscript or delete the content.

Leave a Comment