stringtranslate.com

compensación de

La macro offsetof() de C es una característica de la biblioteca ANSI C que se encuentra en stddef.h . Evalúa el desplazamiento (en bytes) de un miembro determinado dentro de una estructura o tipo de unión , una expresión de tipo size_t . La macro toma dos parámetros , el primero es un nombre de estructura o unión, y el segundo es el nombre de un subobjeto de la estructura/unión que no es un campo de bits . No puede describirse como un prototipo C. [1]offsetof()

Implementación

La implementación "tradicional" de la macro dependía de que el compilador obtuviera el desplazamiento de un miembro especificando una estructura hipotética que comienza en la dirección cero:

#definir desplazamiento de (st, m) \  ((size_t)&(((st *)0)->m))

Esto puede entenderse como tomar un puntero nulo de estructura de tipo st y luego obtener la dirección del miembro m dentro de dicha estructura. Si bien esta implementación funciona correctamente en muchos compiladores, ha generado cierto debate sobre si se trata de un comportamiento indefinido según el estándar C, [2] ya que parece implicar una desreferencia de un puntero nulo (aunque, según el estándar, la sección 6.6 Expresiones constantes, párrafo 9, la operación no accede al valor del objeto). También tiende a producir diagnósticos del compilador confusos si uno de los argumentos está mal escrito. [ cita necesaria ]

Una alternativa es:

#definir desplazamiento de (st, m) \  ((size_t)((char *)&((st *)0)->m - (char *)0))

Puede especificarse de esta manera porque el estándar no especifica que la representación interna del puntero nulo esté en la dirección cero. Por lo tanto, es necesario hacer la diferencia entre la dirección del miembro y la dirección base. Nuevamente, dado que se trata de expresiones constantes, se pueden calcular en tiempo de compilación y no necesariamente en tiempo de ejecución.

Algunos compiladores modernos (como GCC ) definen la macro utilizando una forma especial (como una extensión del lenguaje), por ejemplo, [3]

#definir desplazamiento de (st, m) \  __builtin_offsetof (st, m)

Esta función incorporada es especialmente útil con clases de C++ que declaran un operador unario personalizado & . [4]

Uso

Es útil al implementar estructuras de datos genéricas en C. Por ejemplo, el kernel de Linux usa offsetof() para implementar container_of() , lo que permite que algo así como un tipo mixin encuentre la estructura que lo contiene: [5]

#define contenedor_de(ptr, tipo, miembro) ({ \  const tipode( ((tipo *)0)->miembro ) *__mptr = (ptr); \  (tipo *)( (char *)__mptr - offsetof(tipo, miembro) );})

Esta macro se utiliza para recuperar una estructura adjunta desde un puntero a un elemento anidado, como esta iteración de una lista vinculada de objetos my_struct :

estructura my_struct { const char * nombre ; estructura lista_nodo lista ; };        estructura externa list_node * list_next ( estructura list_node * );      struct list_node * actual = /* ... */ mientras ( actual ! = NULL ) { struct my_struct * elemento = contenedor_of ( actual , struct my_struct , lista ); printf ( "%s \n " , elemento -> nombre ); actual = lista_siguiente ( & elemento -> lista ); }                     

La implementación del kernel de Linux de container_of utiliza una extensión GNU C llamada expresiones de declaración . [6] Es posible que se haya utilizado una expresión de declaración para garantizar la seguridad de tipos y, por lo tanto, eliminar posibles errores accidentales. Sin embargo, existe una manera de implementar el mismo comportamiento sin utilizar expresiones de declaración y al mismo tiempo garantizar la seguridad de tipos:

#define contenedor_de(ptr, tipo, miembro) ((tipo *)((char *)(1? (ptr): &((tipo *)0)->miembro) - offsetof(tipo, miembro)))

A primera vista, esta implementación puede parecer más compleja de lo necesario y el uso inusual del operador condicional puede parecer fuera de lugar. Es posible una implementación más sencilla:

#define contenedor_de(ptr, tipo, miembro) ((tipo *)((char *)(ptr) - offsetof(tipo, miembro)))

Esta implementación también serviría para el mismo propósito; sin embargo, hay una omisión fundamental en términos de la implementación original del kernel de Linux. El tipo de ptr nunca se compara con el tipo de miembro, esto es algo que la implementación del kernel de Linux detectaría.

En la implementación de tipo verificado antes mencionada, la verificación se realiza mediante el uso inusual del operador condicional. Las restricciones del operador condicional especifican que si los operandos del operador condicional son ambos punteros a un tipo, ambos deben ser punteros a tipos compatibles. En este caso, a pesar de que el valor del tercer operando de la expresión condicional nunca se utilizará, el compilador debe realizar una verificación para garantizar que (ptr)ambos &((type *)0)->membertipos de puntero sean compatibles.

Limitaciones

El uso de offsetofestá limitado a tipos POD en C++98 , clases de diseño estándar en C++11 , [7] y más casos se admiten condicionalmente en C++17 , [8] de lo contrario tiene un comportamiento indefinido. Si bien la mayoría de los compiladores generarán un resultado correcto incluso en los casos que no respetan el estándar, hay casos extremos en los que offsetof producirá un valor incorrecto, generará una advertencia o error en tiempo de compilación, o bloqueará completamente el programa. Este es especialmente el caso de la herencia virtual. [9] El siguiente programa generará varias advertencias e imprimirá resultados obviamente sospechosos cuando se compila con gcc 4.7.3 en una arquitectura amd64:

#incluir <stddef.h> #incluir <stdio.h>  estructura A { int a ; muñeco vacío virtual () {} };       estructura B : virtual pública A { int b ; };      int main () { printf ( "desplazamiento de (A, a): %zu \n " , desplazamiento de ( A , a )); printf ( "desplazamiento de (B, b) : %zu \n " , desplazamiento de ( B , b )); devolver 0 ; }         

La salida es:

compensación de (A, a): 8compensación de (B, b): 8

Referencias

  1. ^ "compensación de referencia". MSDN . Consultado el 19 de septiembre de 2010 .
  2. ^ "¿&((struct name *)NULL -> b) causa un comportamiento indefinido en C11?" . Consultado el 7 de febrero de 2015 .
  3. ^ "Compensación de referencia del CCG". Fundación de Software Libre . Consultado el 19 de septiembre de 2010 .
  4. ^ "¿Cuál es el propósito y el tipo de retorno del operador __builtin_offsetof?" . Consultado el 20 de octubre de 2012 .
  5. ^ Greg Kroah-Hartman (junio de 2003). "contenedor_de()". Diario de Linux . Consultado el 19 de septiembre de 2010 .
  6. ^ "Declaraciones y Declaraciones en Expresiones". Fundación de Software Libre . Consultado el 1 de enero de 2016 .
  7. ^ "compensación de referencia". cplusplus.com . Consultado el 1 de abril de 2016 .
  8. ^ "compensación de referencia". cppreference.com . Consultado el 20 de julio de 2020 .
  9. ^ Steve Jessop (julio de 2009). "¿Por qué no se puede utilizar offsetof en estructuras que no sean POD en C++?". Desbordamiento de pila . Consultado el 1 de abril de 2016 .