Pay attention to + star standard public number, miss the latest article





One, foreword

Half a month ago to write the article about pointer bottom principle, has won the acceptance of many friends (link: C language pointer – from the underlying principle to design techniques, using graphic and code to help you explain thoroughly), especially for junior partner to just learn C, it is easy to fundamentally understand pointer what and how to use, this also let I firmly believe that a word; The article written by heart will be felt by readers! When WRITING this article, I made an outline, and when I got to the end, I found that there were more than 10,000 words, but there was still one last topic left on the outline. If you continue to write, the volume of the article will become too large, so a tail will be left.

Today, I will add this tail: mainly introduces Pointers in application programming, often used techniques. If the previous article can be regarded as the level of “Tao”, then this article belongs to the level of “shu”. Mainly through 8 examples to show in C language applications, about the use of Pointers common routines, I hope to bring you harvest.

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

Two, eight examples

1. Appetizer: Modify the data in the tonic function

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); }Copy the code

This code doesn’t need to be explained. It’s easy to see. It would be an insult to intelligence to explain more.

2. Allocate system resources in the called function

The purpose of this code is to allocate size bytes of space from the heap in the called function and return the pData pointer in the calling function.

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); }Copy the code
2.1 Incorrect Usage

When demo2_malloc_heap_error is called, the parameter buff is a char* pointer whose value is equal to the value of the pData variable (both NULL).

After executing the malloc statement in the called function, the address space obtained from the heap is assigned to the buF, meaning that it refers to the new address space, and the pData is still NULL. The memory model is as follows:

As you can see from the figure, pData is always NULL in memory and does not point to any heap space. In addition, since the buf parameter is placed on the stack of the function, the requested heap space is leaked when it is returned from the called function.

2.2 Correct Usage

When demo2_malloc_heap_error is called, the parameter buf is a char* secondary pointer, meaning that the value in buf is the address of another pointer variable. In this case, buf is the address of pData. The memory model is as follows:

*buf = &pdata; *buf = &pdata; *buf = &pdata;

When returned from the called function, pData gets the correct amount of heap space. Don’t forget to release it after use.

3. Pass the function pointer

As we know from the previous article, the function name itself represents an address in which a sequence of instructions defined in the function body is stored. The function is executed by adding a call character (parentheses) to the address. In real programs, function names are often passed as function parameters.

If you are familiar with C++, you will know that you can pass in the user’s own algorithm function to perform various algorithm operations on the container type data in the standard library (if you do not pass in the function, the standard library uses the default).

The following is an example code that sorts an array of int rows. The last argument to the sort function demo3_handle_DATA is a function pointer, so you need to pass in a specific sort algorithm function. There are two candidate functions available in the example:

  1. Descending order: DEMO3_ALGORITHm_decend;
  2. 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");
}
Copy the code

The function pointer pf points to the address of the function passed in, and can be called when sorting.

4. Pointer to structure

In embedded development, Pointers to structures are especially widely used. Here, a control instruction in smart home is taken as an example. In a smart home system, there are a variety of devices (sockets, lights, electric curtains, etc.), the control instructions of each device are not the same, so you can put in the front of the control command structure of each device, all instructions need, common member variables. These variables can be called instruction headers (which contain an enumerated variable representing the type of the command).

When processing a control instruction, first use a general command (instruction head) pointer to receive instruction, and then according to the command type enumeration variables to distinguish, the control instruction is forced into the specific data structure of that device, so that you can obtain the specific control data in the control instruction.

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

// Enumeration of instruction types typedef enum _CMD_TYPE_ {CMD_TYPE_CONTROL_SWITCH = 1, CMD_TYPE_CONTROL_LAMP,} CMD_TYPE; // Typepedef struct _CmdBase_ {CMD_TYPE cmdType; // Instruction type int deviceId; // device Id} CmdBase; Typepedef struct _CmdControlSwitch_ {// The first two arguments are the instruction header CMD_TYPE cmdType; int deviceId; // There is private data for the directive int slot; Int state; // 0: off, 1: on} CmdControlSwitch; Typedef struct _CmdControlLamp_ {// The first two arguments are the command header CMD_TYPE cmdType; int deviceId; // There is private data for this directive int color; // Color int brightness; } CmdControlLamp; Void demo4_control_device(CmdBase * PCMD) {// According to the command type in the command header, If (CMD_TYPE_CONTROL_SWITCH == PCMD ->cmdType) {// Type coercion 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); Cmdtype_control_switch cmd1 = {CMD_TYPE_CONTROL_SWITCH, 1, 3, 0}; demo4_control_device(&cmd1); CmdControlLamp cmd2 = {CMD_TYPE_CONTROL_LAMP, 2, 0x112233, 90}; demo4_control_device(&cmd2); }Copy the code

5. Function pointer array

This example was demonstrated in the previous article, but is posted here again for completeness.

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));
}
Copy the code

6. Use flexible arrays in structs

Without explaining the concepts, let’s look at a code example:

Typedef struct _ArraryMemberStruct_NotGood_ {int num; char *data; } ArraryMemberStruct_NotGood; Void demo6_not_good() {// Print the memory sizeof the structure int size = sizeof(ArraryMemberStruct_NotGood); printf("size = %d \n", size); ArraryMemberStruct_NotGood *ams = (ArraryMemberStruct_NotGood *)malloc(size); ams->num = 1; 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); }Copy the code

On my computer, it printed the following:

As you can see, the structure has a total of 8 bytes (4 bytes for ints and 4 bytes for Pointers).

The data member in the structure is a pointer variable that requires a separate space to use. After the structure is used, data must be released first, and then ams, the structure pointer, must be released in the correct order. Is it a bit troublesome to use it like this?

Therefore, the C99 standard defines the flexible array member syntax, which can be used directly on the code:

Typedef struct _ArraryMemberStruct_Good_ {int num; typedef struct _ArraryMemberStruct_Good_ {int num; char data[]; } ArraryMemberStruct_Good; Void demo6_good() {// Print the sizeof the structure 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 space free(ams); }Copy the code

The print result is as follows:

There are several differences from the first example:

  1. The size of the structure becomes 4;
  2. In addition to the size of the structure itself, we also apply for the size of the space needed by data.
  3. There is no need to allocate space separately for data;
  4. To release space, directly release the structure pointer.

Is it much easier to use? ! That’s the benefit of flexible arrays.

Syntactically, a flexible array is an array in which the number of last elements is unknown. It can also be interpreted as having a length of 0, so you can call the structure variable length.

As mentioned earlier, the array name represents an address, which is a constant address. In a structure, the array name is just a symbol that represents an offset and does not take up any specific space.

In addition, flexible arrays can be of any type. This example will give you a lot of experience, and you will see this usage in many communication class processing scenarios.

7. Use a pointer to get the offset of a member variable in the structure

In a structure variable, you can use pointer manipulation techniques to get the address of a member variable, the address from the start of the structure variable, and the offset between them.

You can see this technique used in many places in the Linux kernel code, 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)); }Copy the code

First look at the print result:

The information printed in the previous four lines does not need to be explained, but can be understood by looking directly at the memory model below.

Char * char* char* char* char* char* char* char* char* char* char* char* char*

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

The following statement needs to be understood:

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

The number 0 is thought of as an address, which is a pointer. As explained in the previous article, Pointers represent a chunk of memory, and you can tell the compiler what you think of the data in that space, and the compiler will do what you want.

We now treat the data at address 0 as an OffsetStruct structure variable (which is told to the compiler by casting). This gives us an OffsetStruct pointer (the green line below) and a member of the pointer variable (the blue line below). We then get the address of A by taking the address symbol & (orange line), and force that address to size_t (red line).

Since the structure pointer variable starts at address 0, the address of the member variable A is the offset from the starting address of the structure variable.

If the above description is difficult to pronounce, please read it again with this picture:

The last type of print statement that gets the offset from a macro definition is the one that abstracts the code into a macro definition.

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

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

What is the use of obtaining this offset? Take a look at example 8 below.

8. Obtain the pointer of the structure by using the pointer of the member variable in the structure

Title is also a mouthful, directly combined with the code:

typedef struct _OffsetStruct_ {
    int a;
    int b;
    int c;
} OffsetStruct;
Copy the code

If we have an OffsetStruct variable OS, we only know the address (pointer) of the member variable C in OS, so we want to get the address (pointer) of the variable OS. That’s the purpose of the title.

The macro definition of container_OF in the following code also comes from the Linux kernel.

#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) ); }) void demo8() {int n = 1; typeof(n) m = 2; // define the same type of variable m printf("n = %d, m = %d \n", n, m); // define struct variables and initialize 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; 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); }Copy the code

First look at the print result:

The first step is to be clear about the types of parameters in the macro definition:

  1. PTR: pointer to a member variable;
  2. Type: structure type.
  3. Member: the name of the member variable;

The key here is to understand the macro definition of container_of, which can be broken down into two parts:

Statement 1 in macro definition:

  1. Green line: treat the number 0 as a pointer, forcing the structure type to type;
  2. Blue line: Gets the member variable member in the pointer to this structure;
  3. The orange line: using the typeof keyword, get the typeof the member and define a pointer variable __mptr for that type.
  4. Red line: assigns the macro parameter PTR to the __mptr variable;

Statement 2 in macro definition:

  1. Use the offset macro definition in Demo7 to get the offset from the starting address of the member variable. This member variable pointer is already known: __mptr.
  2. The blue line: Subtract __mptr’s offset from the starting address of the structure variable to get the starting address of the structure variable.
  3. Orange line: Finally convert this pointer (char* at this point) to a pointer of type type;

Third, summary

After mastering the above 8 Pointers, dealing with subcharacters, arrays, linked lists, etc., is basically a matter of proficiency and workload. Hope everyone can use good pointer this artifact, improve the efficiency of program execution.

Original is not easy, if this article is helpful, please forward, share with your friends, Daoge here to express thanks!



Author: Doggo (public id: IOT Town) Zhihu: Doggo B station: Doggo share Nuggets: Doggo share CSDN: Doggo share

















[1] C Pointers – From basic principles to fancy tricks, [3] The underlying debugging principle of GDB is so simple. [4] Double buffering technology in producer and consumer mode. [5] About encryption, certificates. [6] In-depth LUA scripting language. Let you thoroughly understand the debugging principle [7] a printf(structure variable) caused by blood