¿Cómo funciona la macro C offsetof? [duplicate]

6 minutos de lectura

avatar de usuario
samuel liew

Posible duplicado:

¿Por qué funciona este código C?
¿Cómo usas offsetof() en una estructura?

Leí sobre esta macro offsetof en Internet, pero no explica para qué se usa.

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))

¿Qué está tratando de hacer y cuál es la ventaja de usarlo?

  • Que offsetof la macro es incorrecta Deben echar a size_tno inty probablemente deberían restar (char*)0 del resultado antes de lanzar aunque sea una constante de puntero nulo.

    – Chris Lutz

    26 de octubre de 2011 a las 2:27

avatar de usuario
Eamonn O’Brien-Strain

R.. tiene razón en su respuesta a la segunda parte de su pregunta: no se recomienda este código cuando se usa un compilador de C moderno.

Pero para responder a la primera parte de su pregunta, lo que realmente está haciendo es:

(
  (int)(         // 4.
    &( (         // 3.
      (a*)(0)    // 1.
     )->b )      // 2.
  )
)

Trabajando de adentro hacia afuera, esto es…

  1. Convertir el valor cero al tipo de puntero de estructura a*
  2. Obtener el campo de estructura b de este objeto de estructura (ubicado ilegalmente)
  3. Obtener la dirección de este b campo
  4. Enviar la dirección a un int

Conceptualmente, esto es colocar un objeto de estructura en la dirección de memoria cero y luego averiguar cuál es la dirección de un campo en particular. Esto podría permitirle calcular las compensaciones en la memoria de cada campo en una estructura para que pueda escribir sus propios serializadores y deserializadores para convertir estructuras hacia y desde matrices de bytes.

Por supuesto, si realmente eliminara la referencia a un puntero cero, su programa fallaría, pero en realidad todo sucede en el compilador y no se elimina la referencia real de ningún puntero cero en tiempo de ejecución.

En la mayoría de los sistemas originales que C se ejecutaba en el tamaño de un int era de 32 bits y era lo mismo que un puntero, por lo que realmente funcionó.

  • ¡Excelente! Gracias. La clave para mí fue placing a struct object at memory address zero and then finding out at what the address of a particular field is.

    – Timur Fayzrakhmanov

    17 oct 2018 a las 10:43


avatar de usuario
R.. GitHub DEJAR DE AYUDAR A ICE

No tiene ventajas y no debe usarse, ya que invoca un comportamiento indefinido (y usa el tipo incorrecto: int en lugar de size_t).

El estándar C define un offsetof macro en stddef.h que realmente funciona, para los casos en los que necesita el desplazamiento de un elemento en una estructura, como:

#include <stddef.h>

struct foo {
    int a;
    int b;
    char *c;
};

struct struct_desc {
    const char *name;
    int type;
    size_t off;
};

static const struct struct_desc foo_desc[] = {
    { "a", INT, offsetof(struct foo, a) },
    { "b", INT, offsetof(struct foo, b) },
    { "c", CHARPTR, offsetof(struct foo, c) },
};

que le permitiría completar programáticamente los campos de un struct foo por nombre, por ejemplo, al leer un archivo JSON.

  • El estandar offsetof macro de stddef.h no invoca UB. Definir su propio truco para calcular compensaciones de esta manera invoca UB.

    – R.. GitHub DEJA DE AYUDAR A ICE

    26 de octubre de 2011 a las 2:24

  • @Adrian: No dijo, “definir su propia versión de la macro provoca un comportamiento indefinido”. Específicamente dijo: “Definir su propio truco para calcular las compensaciones Por aquí invoca UB.” En el código, en este punto: ((a*)(0))-> ha invocado un comportamiento indefinido al eliminar la referencia a nulo.

    – GManNickG

    26 de octubre de 2011 a las 2:29


  • @AdrianCornish: La implementación se permite definir offsetof sin embargo, le gusta siempre que implemente el comportamiento correcto. Su aplicación no tiene este privilegio porque no puede definir el comportamiento de nada; solo puede usar construcciones de lenguaje ya definidas. Así es como funciona C.

    – R.. GitHub DEJA DE AYUDAR A ICE

    26 de octubre de 2011 a las 2:42


  • 6.5.2.3 no utiliza la palabra “desreferencia”, pero la especifica como “el miembro nombrado del objeto al que apunta la primera expresión”. Ya que (a*)(0) no apunta a un objeto de tipo ael comportamiento es indefinido (en virtud de no estar definido).

    – R.. GitHub DEJA DE AYUDAR A ICE

    26 de octubre de 2011 a las 2:49

  • El texto que cita es irrelevante. Ningún puntero a tipo de objeto incompleto o incompleto se convierte en un puntero a anular en la macro falsa.

    – R.. GitHub DEJA DE AYUDAR A ICE

    26 de octubre de 2011 a las 2:57

Es encontrar el desplazamiento de bytes de un miembro particular de un struct. Por ejemplo, si tuviera la siguiente estructura:

struct MyStruct
{
    double d;
    int i;
    void *p;
};

Entonces tendrías offsetOf(MyStruct, d) == 0, offsetOf(MyStruct, i) == 8y offsetOf(MyStruct, p) == 12 (es decir, el miembro llamado d es 0 bytes desde el inicio de la estructura, etc.).

La forma en que funciona es que pretende que existe una instancia de su estructura en la dirección 0 (la ((a*)(0)) part), y luego toma la dirección del miembro de estructura deseado y lo convierte en un número entero. Aunque eliminar la referencia de un objeto en la dirección 0 normalmente sería un error, está bien tomar la dirección porque el operador de dirección & y la desreferencia del miembro -> se anulan entre sí.

Por lo general, se usa para marcos de serialización generalizados. Si tiene un código para convertir entre algún tipo de datos de cable (por ejemplo, bytes en un archivo o de la red) y estructuras de datos en memoria, a menudo es conveniente crear una asignación desde el nombre del miembro hasta el desplazamiento del miembro, para que pueda serializar o deserializar valores de forma genérica.

  • La pregunta es C, todavía tenemos que usar struct MyStruct. 😉

    – Chris Lutz

    26 de octubre de 2011 a las 2:29

avatar de usuario
Adrián Cornualles

La implementación de la macro offsetof es realmente irrelevante.

El estándar C real lo define como en 7.17.3:

offsetof(type, member-designator)

que se expande a una expresión constante entera que tiene tipo size_t, cuyo valor es el desplazamiento en bytes, al miembro de estructura (designado por miembro-designador), desde el comienzo de su estructura (designado por tipo). El designador de tipo y miembro debe ser tal que dado static type t;.

Confía en la respuesta de Adam Rosenfield.

R es completamente incorrecto y tiene muchos usos, especialmente poder saber cuándo el código no es portátil entre plataformas.

(Está bien, es C++, pero lo usamos en aserciones de tiempo de compilación de plantillas estáticas para asegurarnos de que nuestras estructuras de datos no cambien de tamaño entre plataformas/versiones).

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad