These skills of using C pointers will be raised to a level immediately after mastering


This is Brother Dao's 016th original

Follow + star public account , don’t miss the latest articles
Insert picture description here


I. Introduction

The article about the lowest level of pointers that I wrote half a month ago has been recognized by many friends (link: C language pointers-from the underlying principles to fancy skills, using graphics and code to help you explain thoroughly ), especially For those who are just learning the C language, it is easy to fundamentally understand what pointers are and how to use them . This also makes me believe in one sentence; articles written with heart will surely be felt by readers ! When I was writing this article, I made an outline. When I wrote it later, I found that it had exceeded 10,000 words, but there was one last topic in the outline that was not written. If you continue to write it down, the volume of the article will be too big, so it will leave a tail.

Today, I will fill up this tail: mainly introduce pointers in application programming, frequently used skills . If the previous article is barely at the "Tao" level, then this article belongs to the "Technology" level. Mainly through 8 sample programs to show the common routines of pointer use in C language applications, I hope it can bring you gains.

I remember that when I was learning C language on campus, Teacher Huang Fengliang from Nanjing Normal University spent most of the class explaining pointers to us. Now I remember the words that the teacher said most clearly: pointers are addresses, and addresses are pointers!

Two, eight examples

1. Appetizer: modify the data in the main calling function

// 交换 2 个 int 型数据
void demo1_swap_data(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void demo1()
{
    int i = 1;
    int j = 2;
    printf("before: i = %d, j = %d \n", i, j);
    demo1_swap_data(&i, &j);
    printf("after:  i = %d, j = %d \n", i, j);
}

This code does not need to be explained, everyone will understand it at first glance. If you explain too much, it seems to be an insult to IQ.

2. In the called function, allocate system resources

Purpose of the code is: the called function allocated from the heap area sizebyte space, is returned to the calling function the pDatapointer.

void demo2_malloc_heap_error(char *buf, int size)
{
    buf = (char *)malloc(size);
    printf("buf = 0x%x \n", buf);
}

void demo2_malloc_heap_ok(char **buf, int size)
{
    *buf = (char *)malloc(size);
    printf("*buf = 0x%x \n", *buf);
}

void demo2()
{
    int size = 1024;
    char *pData = NULL;

    // 错误用法
    demo2_malloc_heap_error(pData, size);
    printf("&pData = 0x%x, pData = 0x%x \n", &pData, pData);

    // 正确用法
    demo2_malloc_heap_ok(&pData, size);
    printf("&pData = 0x%x, pData = 0x%x \n", &pData, pData);
    free(pData);
}
2.1 Wrong usage

Just entered the called function demo2_malloc_heap_errortime, the parameter buffis a char * pointer type , its value is equal to the pDatavalue of the variable, that is buffthe pDatasame value (are NULL) , as shown in FIG memory model:

In the called function is executed mallocafter the statement, obtained from the application heap address space assigned to buf, that it was pointed at the new address space, but pDatathere is still theNULL memory model is as follows:

As you can see from the figure, pDatathe memory is always NULLthere, without pointing to any heap space . Further, since the parameter bufis a function on the stack area , and when returning from the called function, this application heap space was leaked .

2.2 Correct usage

Just entered the called function demo2_malloc_heap_errorwhen the parameter bufis a two char * pointer type , that is bufthe value in the pointer is the address of another variable, in this example the value is in the address pointer variable , the memory model is as follows : bufpData

In the called function is executed mallocafter the statement, obtained from the application heap address space assigned to the * buf, because buf = &pData, it is the equivalent of * buf pData, then the application address space obtained from the stack area to assign pDatavariables , memory model is as follows:

After returning from the called function, pDatayou will get a piece of heap space correctly. Don't forget to release it actively after use .

3. Pass function pointer

From the previous article, we know that the function name itself represents an address , and a series of instruction codes defined in the function body are stored in this address. Just add a call sign (parentheses) after the address to enter the function In execution. In actual programs, function names are often passed as function parameters.

Friends who are familiar with C++ know that when performing various algorithm operations on container-type data in the standard library, you can pass in the algorithm function provided by the user (if you do not pass in the function, the standard library uses the default).

The following is a sample code, an int array rows are, the sort function demo3_handle_datathe last parameter is a function pointer , and therefore need to pass a particular sorting algorithm function. There are 2 candidate functions that can be used in the example:

  1. Sorting in descending order: demo3_algorithm_decend;
  2. Sort in ascending order: demo3_algorithm_ascend;
typedef int BOOL;
#define FALSE 0
#define TRUE  1

BOOL demo3_algorithm_decend(int a, int b)
{
    return a > b;
}

BOOL demo3_algorithm_ascend(int a, int b)
{
    return a < b;
}

typedef BOOL (*Func)(int, int);
void demo3_handle_data(int *data, int size, Func pf)
{
    for (int i = 0; i < size - 1; ++i)
    {
        for (int j = 0; j < size - 1 - i; ++j)
        {
            // 调用传入的排序函数
            if (pf(data[j], data[j+1]))
            {
                int tmp = data[j];
                data[j] = data[j + 1];
                data[j + 1] = tmp;
            }
        }
    }
}

void demo3()
{
    int a[5] = {5, 1, 9, 2, 6};
    int size = sizeof(a)/sizeof(int);
    // 调用排序函数,需要传递排序算法函数
    //demo3_handle_data(a, size, demo3_algorithm_decend); // 降序排列
    demo3_handle_data(a, size, demo3_algorithm_ascend);   // 升序排列
    for (int i = 0; i < size; ++i)
        printf("%d ", a[i]);
    printf("\n");
}

This would not drawing, the function pointer pfpoints to the address of the function passed , in the sort of time called directly on it.

4. Pointer to the structure

In the embedded development, the pointer to the structure is particularly widely used. Here, a control instruction in the smart home is used as an example. In a smart home system, there are a variety of equipment (sockets, lights, electric curtains, etc.), each device control command is not the same, it is possible to control instruction structure of each device in the most Earlier , place the common member variables required by all instructions . These variables can be called instruction headers (the instruction header contains an enumeration variable representing the command type).

When processing a control command, first use a pointer of a general command (command header) to receive the command, and then distinguish it according to the command type enumerated variable , and force the control command to be converted into the data structure of the specific device , so that it can be obtained It's time for the specific control data in the control command.

In essence, it is similar to the concept of interface and base class in Java/C++ .

// 指令类型枚举
typedef enum _CMD_TYPE_ {
    CMD_TYPE_CONTROL_SWITCH = 1,
    CMD_TYPE_CONTROL_LAMP,
} CMD_TYPE;

// 通用的指令数据结构(指令头)
typedef struct _CmdBase_ {
    CMD_TYPE cmdType; // 指令类型
    int deviceId;     // 设备 Id
} CmdBase;

typedef struct _CmdControlSwitch_ {
    // 前 2 个参数是指令头
    CMD_TYPE cmdType;   
    int deviceId;
    
    // 下面都有这个指令私有的数据
    int slot;  // 排插上的哪个插口
    int state; // 0:断开, 1:接通
} CmdControlSwitch;

typedef struct _CmdControlLamp_ {
    // 前 2 个参数是指令头
    CMD_TYPE cmdType;
    int deviceId;
    
    // 下面都有这个指令私有的数据
    int color;      // 颜色
    int brightness; // 亮度
} CmdControlLamp;

// 参数是指令头指针
void demo4_control_device(CmdBase *pcmd)
{
    // 根据指令头中的命令类型,把指令强制转换成具体设备的指令
    if (CMD_TYPE_CONTROL_SWITCH == pcmd->cmdType)
    {
        // 类型强制转换
        CmdControlSwitch *cmd = pcmd;
        printf("control switch. slot = %d, state = %d \n", cmd->slot, cmd->state);
    }
    else if (CMD_TYPE_CONTROL_LAMP == pcmd->cmdType)
    {
        // 类型强制转换
        CmdControlLamp * cmd = pcmd;
        printf("control lamp.   color = 0x%x, brightness = %d \n", cmd->color, cmd->brightness);
    }
}

void demo4()
{
    // 指令1:控制一个开关
    CmdControlSwitch cmd1 = {CMD_TYPE_CONTROL_SWITCH, 1, 3, 0};
    demo4_control_device(&cmd1);

    // 指令2:控制一个灯泡
    CmdControlLamp cmd2 = {CMD_TYPE_CONTROL_LAMP, 2, 0x112233, 90};
    demo4_control_device(&cmd2);
}

5. Function pointer array

This example was demonstrated in the previous article. For completeness, I will post it here.

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }

void demo5()
{
    int a = 4, b = 2;
    int (*p[4])(int, int);
    p[0] = add;
    p[1] = sub;
    p[2] = mul;
    p[3] = divide;
    printf("%d + %d = %d \n", a, b, p[0](a, b));
    printf("%d - %d = %d \n", a, b, p[1](a, b));
    printf("%d * %d = %d \n", a, b, p[2](a, b));
    printf("%d / %d = %d \n", a, b, p[3](a, b));
}

6. Use flexible arrays in structures

Without explaining the concept, let's first look at a code example:

// 一个结构体,成员变量 data 是指针
typedef struct _ArraryMemberStruct_NotGood_ {
    int num;
    char *data;
} ArraryMemberStruct_NotGood;

void demo6_not_good()
{
    // 打印结构体的内存大小
    int size = sizeof(ArraryMemberStruct_NotGood);
    printf("size = %d \n", size);

    // 分配一个结构体指针
    ArraryMemberStruct_NotGood *ams = (ArraryMemberStruct_NotGood *)malloc(size);
    ams->num = 1;

    // 为结构体中的 data 指针分配空间
    ams->data = (char *)malloc(1024);
    strcpy(ams->data, "hello");
    printf("ams->data = %s \n", ams->data);

    // 打印结构体指针、成员变量的地址
    printf("ams = 0x%x \n", ams);
    printf("ams->num  = 0x%x \n", &ams->num);
    printf("ams->data = 0x%x \n", ams->data);

    // 释放空间
    free(ams->data);
    free(ams);
}

On my computer, the print result is as follows:

It can be seen that the structure has a total of 8 bytes (int type occupies 4 bytes, pointer type occupies 4 bytes).

The structure of datathe members is a pointer variable , for it requires a separate application can use a space. And after the structure is used, it needs to be released first data, and then the structure pointer ams, the order cannot be wrong .
Is it a bit troublesome to use this way?

Therefore, the C99 standard defines a grammar: flexible array member (flexible array) , directly upload the code (if the following code encounters a warning when compiling, please check the compiler's support for this grammar):

// 一个结构体,成员变量是未指明大小的数组
typedef struct _ArraryMemberStruct_Good_ {
    int num;
    char data[];
} ArraryMemberStruct_Good;

void demo6_good()
{
    // 打印结构体的大小
    int size = sizeof(ArraryMemberStruct_Good);
    printf("size = %d \n", size);

    // 为结构体指针分配空间
    ArraryMemberStruct_Good *ams = (ArraryMemberStruct_Good *)malloc(size + 1024);

    strcpy(ams->data, "hello");
    printf("ams->data = %s \n", ams->data);

    // 打印结构体指针、成员变量的地址
    printf("ams = 0x%x \n", ams);
    printf("ams->num  = 0x%x \n", &ams->num);
    printf("ams->data = 0x%x \n", ams->data);

    // 释放空间
    free(ams);
}

The print result is as follows:

There are several differences from the first example :

  1. The size of the structure becomes 4;
  2. When allocating space for the structure pointer, in addition to the size of the structure itself, it also applies for the space required by data;
  3. No need to allocate space separately for data;
  4. When releasing space, just release the structure pointer directly;

Is it easier to use? ! This is the benefit of flexible arrays.

Syntactically speaking, a flexible array refers to an array with an unknown number of last elements in the structure, and it can also be understood as a length of 0, so this structure can be called variable-length.

As mentioned earlier, the array name represents an address, which is a constant address constant. In the structure, the array name is just a symbol, it only represents an offset, and does not occupy specific space.

In addition, the flexible array can be of any type . Everyone has a lot of experience in the example here. This usage is often seen in many communication processing scenarios.

7. Get the offset of the member variable in the structure through the pointer

This title seems a bit confusing to read. Split it up: In a structure variable , you can use pointer manipulation techniques to obtain the address of a member variable , the starting address of the structure variable , and the offset between them .

You can see that this technique is used in many places in the Linux kernel code . The code is as follows:

#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))

typedef struct _OffsetStruct_ {
    int a;
    int b;
    int c;
} OffsetStruct;

void demo7()
{
    OffsetStruct os;
    // 打印结构体变量、成员变量的地址
    printf("&os = 0x%x \n", &os);
    printf("&os->a = 0x%x \n", &os.a);
    printf("&os->b = 0x%x \n", &os.b);
    printf("&os->c = 0x%x \n", &os.c);
    printf("===== \n");
    // 打印成员变量地址,与结构体变量开始地址,之间的偏移量
    printf("offset: a = %d \n", (char *)&os.a - (char *)&os);
    printf("offset: b = %d \n", (char *)&os.b - (char *)&os);
    printf("offset: c = %d \n", (char *)&os.c - (char *)&os);
    printf("===== \n");
    // 通过指针的强制类型转换来获取偏移量
    printf("offset: a = %d \n", (size_t) &((OffsetStruct*)0)->a);
    printf("offset: b = %d \n", (size_t) &((OffsetStruct*)0)->b);
    printf("offset: c = %d \n", (size_t) &((OffsetStruct*)0)->c);
    printf("===== \n");
    // 利用宏定义来得到成员变量的偏移量
    printf("offset: a = %d \n", offsetof(OffsetStruct, a));
    printf("offset: b = %d \n", offsetof(OffsetStruct, b));
    printf("offset: c = %d \n", offsetof(OffsetStruct, c));
}

First look at the print result:

The print information of the first 4 lines does not need to be explained, just look at the memory model below to understand.

The following statement does not need to be explained. It is to subtract the values of the two addresses to get the offset from the start address of the structure variable . Note: the address can be subtracted only after the address is forcibly converted to char*.

printf("offset: a = %d \n", (char *)&os.a - (char *)&os);

The following statement needs to be understood well:

printf("offset: a = %d \n", (size_t) &((OffsetStruct*)0)->a);

The number 0 is regarded as an address, that is, a pointer. As explained in the previous article, the pointer represents a space in the memory. As for what you think of the data in this space, it is up to you. You only need to tell the compiler, and the compiler will operate the data according to your intentions. .

Now we regard the data in the address 0 as an OffsetStruct structure variable (to tell the compiler through forced conversion), so we get an OffsetStruct structure pointer (the green horizontal line in the figure below ), and then get the pointer variable In the member variable a ( blue horizontal line ), and then get the address of a ( orange horizontal line ) by taking the address character & , and finally force this address into size_t type ( red horizontal line ).

Because this structure pointer variable starts at address 0, the address of the member variable a is the offset of a from the start address of the structure variable.

The above description process, if you feel awkward, please read it several times in combination with the following picture:

If the above picture can be understood, then the last type of print statement that obtains the offset through the macro definition will also be understood. It is nothing more than abstracting the code into a macro definition for easy calling :

#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))

printf("offset: a = %d \n", offsetof(OffsetStruct, a));

Some friends may ask: What is the use of obtaining this offset? Then please look at Example 8 below .

8. Get the pointer of the structure through the pointer of the member variable in the structure

The title is also more mouth-watering, and it can be directly combined with the code:

typedef struct _OffsetStruct_ {
    int a;
    int b;
    int c;
} OffsetStruct;

Suppose there is an OffsetStruct structure variable os, we only know the address (pointer) of the member variable c in os, then we want to get the address (pointer) of the variable os, what should we do? This is the purpose described in the title.

Macro definitions in the following code container_ofalso comes from the Linux kernel (when we usually nothing more than digging, you can find a lot of good stuff).

#define container_of(ptr, type, member) ({ \
     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
     (type *)( (char *)__mptr - offsetof(type,member) );})

void demo8()
{
    // 下面 3 行仅仅是演示 typeof 关键字的用法
    int n = 1;
    typeof(n) m = 2;  // 定义相同类型的变量m
    printf("n = %d, m = %d \n", n, m); 

    // 定义结构体变量,并初始化
    OffsetStruct os = {1, 2, 3};
    
    // 打印结构体变量的地址、成员变量的值(方便后面验证)
    printf("&os = 0x%x \n", &os);
    printf("os.a = %d, os.b = %d, os.c = %d \n", os.a, os.b, os.c);

    printf("===== \n");
    
    // 假设只知道某个成员变量的地址
    int *pc = &os.c;
    OffsetStruct *p = NULL;
    
    // 根据成员变量的地址,得到结构体变量的地址
    p = container_of(pc, OffsetStruct, c);
    
    // 打印指针的地址、成员变量的值
    printf("p = 0x%x \n", p);
    printf("p->a = %d, p->b = %d, p->c = %d \n", p->a, p->b, p->c);
}

Look at the print result first:

First of all, we must be clear about the types of parameters in the macro definition:

  1. ptr: pointer to member variable;
  2. type: structure type;
  3. member: the name of the member variable;

The focus here is to understand the macro definition container_of, combined with the following picture, disassemble the macro definition for description:

Analysis of the first sentence in the macro definition :

  1. Green horizontal line: The number 0 is regarded as a pointer, and it is forced to be a structure type;
  2. Blue horizontal line: Get the member variable member in the structure pointer;
  3. Orange horizontal line: Use the typeof keyword to get the type of the member, and then define a pointer variable __mptr of this type;
  4. Red horizontal line: Assign the macro parameter ptr to the __mptr variable;

Analysis of the second sentence in the macro definition :

  1. Green horizontal line: Use the offset macro definition in demo7 to get the offset of the member variable member from the start address of the structure variable, and this member variable pointer has just been known, which is __mptr;
  2. Blue horizontal line: subtract the address of __mptr from the start address of the structure variable to get the start address of the structure variable;
  3. Orange horizontal line: Finally, the pointer (char* type at this time) is forced to be a pointer of structure type;

Three, summary

After mastering the above 8 pointer usages, then dealing with data such as subcharacters, arrays, linked lists, etc., is basically a matter of proficiency and workload.
I hope everyone can make good use of the pointer this artifact to improve the efficiency of program execution.

In the face of code, there will never be bugs; in the face of life, spring blossoms! Good luck!

Originality is not easy. If this article is helpful, please forward it and share it with your friends. Thank you, Brother Dao!


[Original Statement]

OF: Columbia Road (public No.: The IOT of town things )
know almost: Columbia Road
station B: Share Columbia Road
Denver: Columbia Road share
CSDN: Columbia Road Share


I will put ten years of practical experience in embedded development project outputs summary!

If you feel good article, please forward and share to your friends, your support is my greatest motivation continued writing!

Press and hold the QR code in the picture to follow, each article has dry goods.


Reprint: Welcome to reprint, but without the consent of the author, this statement must be retained, and the original link must be given in the article.

Recommended reading

[1] C language pointer-from the underlying principles to fancy skills, with pictures and codes to help you explain thoroughly
[2] Step by step analysis-how to use C to implement object-oriented programming
[3] The original gdb's underlying debugging principle is so simple
[ 4] Double buffering technology in the producer and consumer mode
[5] About encryption and certificates
[6] In- depth LUA scripting language, let you thoroughly understand the principle of debugging
[7] A bloody case caused by printf (structure variable)

Guess you like

Origin blog.csdn.net/u012296253/article/details/113359613