Explicación detallada de container_of macro en el kernel de Linux

¡La última sección se negó a hacer ruedas! Cómo trasplantar y usar la lista vinculada general del kernel de Linux (con implementación de código completa) Al analizar la lista vinculada del kernel de Linux, notamos que el kernel usó inteligentemente la definición de macro container_of al resolver el desplazamiento de estructura. Hoy, analizaremos el kernel en detalle. Cómo resolver la dirección de la variable miembro de estructura.

Cómo se almacena la estructura en la memoria

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;
}

  El resultado de la impresión es el siguiente:

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

  Podemos ver que la dirección de la estructura es la misma que la dirección del primer miembro de la estructura . ¡Por eso antes nos negamos a hacer ruedas! Cómo trasplantar el kernel de Linux y usar la lista genérica (implementación de código completo adjunto) ¿Por qué estamos en la estructura mencionada en struct list_headprimer lugar?

Si no entiende, veamos estos dos ejemplos.
Struct A {int a; char b; int c; char d;}; un desplazamiento es 0, b desplazamiento es 4, c desplazamiento es 8 (mayor que 4 + 1 El múltiplo integral más pequeño de 4), el desplazamiento de d es 12. A está alineado con 4 y el tamaño es 16.
struct B {int a; char b; char c; long d;}; un desplazamiento es 0, b desplazamiento es 4, c desplazamiento es 5, d desplazamiento es 8. B está alineado con 8 y el tamaño es 16.

Inserte la descripción de la imagen aquí

  Podemos ver que las variables miembro en la estructura son en realidad direcciones de desplazamiento almacenadas en la memoria . Es decir , la dirección de la estructura A + la dirección de desplazamiento de la variable miembro = la dirección inicial de la variable miembro de la estructura . Por lo tanto, también podemos inferir la dirección de la estructura A en función de la dirección inicial de la variable de estructura y la dirección de desplazamiento de la variable miembro.

contenedor_de 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)); })

  Primero observe los tres parámetros: ptr es un puntero a una variable miembro, tipo es el tipo de estructura y miembro es el nombre de una variable miembro.

  La función de la macro container_of es pasar la dirección de una variable miembro en la estructura , el nombre de la variable y el tipo de estructura . Encuentre la dirección de la variable de estructura. Lo que se usa aquí es un pequeño truco usando tecnología de compilación, es decir, primero obtenga el desplazamiento del miembro de estructura en la estructura y luego obtenga la dirección de la variable de estructura principal de acuerdo con la dirección de la variable de miembro . El siguiente análisis específico de cada parte:

tipo de

  Primero mire typeof, que se usa para devolver el tipo de una variable . Esta es una función extendida del compilador GCC, lo que significa que typeof está relacionado con el compilador. No es requerido por la especificación del lenguaje C ni es parte de un estándar.

tipo de

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

(((tipo *) 0) -> miembro)

  ((TYPE *)0)Convierta 0 en un puntero de estructura de tipo. En otras palabras, deje que el compilador piense que esta estructura comienza al principio del segmento del programa en 0.Si comienza en la dirección 0, la dirección de la variable miembro que obtenemos es directamente igual a la variable miembro. La dirección de compensación está fuera.
   (((type *)0)->member)Refiérase al miembro MIEMBRO en la estructura.

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 (((tipo ) 0) -> miembro) __mptr = (ptr);

   Este código significa usar typeof () para obtener el tipo de atributo de miembro en la estructura, y luego definir una variable de puntero temporal __mptr de este tipo , y asignar la dirección del miembro apuntado por ptr a __mptr;
  por qué no usar ptr directamente Pero ¿y eso? Creo que podría ser para evitar dañar el contenido señalado por ptr y prt.

offsetof (tipo, miembro))

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

   size_tSe define en la biblioteca C estándar y generalmente se define en la arquitectura de 32 bits como:

typedef unsigned int size_t;

  Y en la arquitectura de 64 bits se define como:

typedef unsigned long size_t;

  Como puede ver en la definición, size_t es un número no negativo, por lo que size_t generalmente se usa para contar (porque contar no requiere un área negativa):

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

  Para que el programa tenga una buena portabilidad, el kernel usa size_t y en lugar de int y unsigned.
((size_t) &((TYPE*)0)->MEMBER)Combinado con la explicación anterior, podemos saber que el significado de esta oración es encontrar MEMBERun valor de compensación relativo a la dirección 0.

(tipo *) ((char *) __ mptr - offsetof (tipo, miembro))

   El significado de esta oración es convertir __mptr al tipo char *, porque el desplazamiento obtenido por offsetof está en bytes. Reste los dos para obtener la posición inicial de la estructura y luego obligue a escribir.

Por ejemplo

#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;	
}

  Los resultados son los siguientes:

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

  La expansión macro puede aclararlo

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;	
}

  ¡Desarrolle un hábito, como primero y luego observe! Si crees que la escritura es buena, bienvenido a seguir, me gusta, favorito, reenviar, ¡gracias!
  El código anterior es el código posterior a la prueba. Si hay errores e irregularidades, indíquelo.

Supongo que te gusta

Origin blog.csdn.net/qq_16933601/article/details/108576447
Recomendado
Clasificación