Detailed explanation of container_of macro in Linux kernel

The last section refused to make wheels! How to transplant and use the general linked list of the Linux kernel (with complete code implementation) We noticed when analyzing the Linux kernel linked list that the kernel cleverly used the container_of macro definition when solving the structure offset. Today, we will analyze the kernel in detail. How to solve the address of the structure member variable.

How the structure is stored in memory

int main()
{
    
    

	Student stu;
	stu.id = 123456;
	strcpy(stu.name,"feizhufeifei");
	stu.math = 90;
	stu.PE = 80;
	printf("Student:%p\r\n",&stu);
	printf("stu.ID:%p\r\n",&stu.ID);
	printf("stu.name:%p\r\n",&stu.name);
	printf("stu.math:%p\r\n",&stu.math);
	return 0;
}

  The print result is as follows:

//结构体的地址
Student:0xffffcbb0
//结构体第一个成员的地址
stu.ID:0xffffcbb0  //偏移地址 +0
stu.name:0xffffcbb4//偏移地址 +4
stu.math:0xffffcbd4//偏移地址 +24

  We can see that the address of the structure is the same as the address of the first member of the structure . This is why we refused to make wheels before ! How to transplant Linux kernel and use generic list (attached full code implementation) Why do we in the structure mentioned in struct list_headthe first place.

If you don’t understand, let’s look at these two examples.
struct A {int a; char b; int c; char d; }; a offset is 0, b offset is 4, c offset is 8 (greater than 4 + 1 The smallest integral multiple of 4), the offset of d is 12. A is aligned to 4 and size is 16.
struct B {int a; char b; char c; long d; }; a offset is 0, b offset is 4, c offset is 5, d offset is 8. B is aligned to 8 and size is 16.

Insert picture description here

  We can see that the member variables in the structure are actually offset addresses stored in memory . That is to say , the address of structure A + the offset address of the member variable = the starting address of the structure member variable . Therefore, we can also infer the address of structure A based on the start address of the structure variable and the offset address of the member variable.

container_of macro

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

  First look at the three parameters. ptr is a pointer to a member variable, type is the type of structure, and member is the name of a member variable.

  The function of the container_of macro is to pass the address of a member variable in the structure , the variable name , and the structure type . Find the address of the structure variable. What is used here is a little trick using compiler technology, that is, first obtain the offset of the structure member in the structure , and then obtain the address of the main structure variable according to the address of the member variable . The following specific analysis of each part:

typeof

  First look at typeof, which is used to return the type of a variable . This is an extended function of the GCC compiler, which means that typeof is compiler related. It is neither required by the C language specification nor part of a standard.

typeof

int main()
{
    
    
	int a = 5;
	//这里定义一个和a类型相同的变量b
	typeof(a) b  = 6;
	printf("%d,%d\r\n",a,b);//5 6
	return 0;
}

(((type *)0)->member)

  ((TYPE *)0)Convert 0 to a structure pointer of type type. In other words, let the compiler think that this structure starts at the beginning of the program segment at 0. If it starts at address 0, the address of the member variable we get is directly equal to the member variable. The offset address is out.
   (((type *)0)->member)Refer to the MEMBER member in the structure.

typedef struct student{
    
    
	int id;
	char name[30];
	int math;
}Student;
int main()
{
    
    
	//这里时把结构体强制转换成0地址,然后打印name的地址。
	printf("%d\r\n",&((Student *)0)->name);//4
	return 0;
}

const typeof(((type )0)->member)__mptr = (ptr);

   This code means to use typeof() to get the type of member attribute in the structure, and then define a temporary pointer variable __mptr of this type , and assign the address of the member pointed to by ptr to __mptr;
  why not use ptr directly But what about it? I think it might be to avoid damaging the content pointed to by ptr and prt.

offsetof(type, member))

((size_t) &((TYPE*)0)->MEMBER)

   size_tIt is defined in the standard C library and is generally defined in the 32-bit architecture as:

typedef unsigned int size_t;

  And in the 64-bit architecture is defined as:

typedef unsigned long size_t;

  As you can see from the definition, size_t is a non-negative number, so size_t is usually used for counting (because counting does not require a negative area):

for(size_t i=0;i<300;i++)

  In order to make the program have good portability, the kernel uses size_t and instead of int and unsigned.
((size_t) &((TYPE*)0)->MEMBER)Combined with the previous explanation, we can know that the meaning of this sentence is to find MEMBERan offset value relative to the 0 address.

(type *)((char *)__mptr - offsetof(type, member))

   The meaning of this sentence is to convert __mptr to char * type, because the offset obtained by offsetof is in bytes. Subtract the two to get the starting position of the structure, and then coerce it to type.

For example

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})
        
typedef struct student
{
    
    
	int id;
	char name[30];
	int math;
}Student;

int main()
{
    
    
  		Student stu;
        Student *sptr = NULL;
		stu.id = 123456;
		strcpy(stu.name,"feizhufeifei");
		stu.math = 90;
        sptr = container_of(&stu.id,Student,id);
        printf("sptr=%p\n",sptr);
        sptr = container_of(&stu.name,Student,name);
        printf("sptr=%p\n",sptr);
        sptr = container_of(&stu.math,Student,id);
        printf("sptr=%p\n",sptr);
        return 0;	
}

  The results are as follows:

sptr=0xffffcb90
sptr=0xffffcb90
sptr=0xffffcbb4

  Macro expansion may make it clearer

int main()
{
    
    
  		Student stu;
        Student *sptr = NULL;
		stu.id = 123456;
		strcpy(stu.name,"feizhufeifei");
		stu.math = 90;
		//展开替换
        sptr = ({
    
     const unsigned char  *__mptr = (&stu.id); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->id) );});
        printf("sptr=%p\n",sptr);
        //展开替换
        sptr = ({
    
     const unsigned char  *__mptr = (&stu.name); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->name) );});
        printf("sptr=%p\n",sptr);
        //展开替换
        sptr = ({
    
     const unsigned int *__mptr = (&stu.math); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->math) );});
        printf("sptr=%p\n",sptr);
        return 0;	
}

  Develop a habit, like first and then watch! If you think the writing is good, welcome to follow, like, favorite, forward, thank you!
  The above code is the code after the test. If there are mistakes and improprieties, please point out.

Guess you like

Origin blog.csdn.net/qq_16933601/article/details/108576447