2010-06-20 28 views
8

Aquí hay una pregunta de entrevista que vi en algún foro. He estado tratando de descubrir cómo funciona, pero no lo entiendo del todo. ¿Alguien podría explicar cómo funciona?Dado un puntero al miembro a dentro de una estructura, escriba una rutina que devuelva un puntero a la estructura

P: Dado un puntero al miembro a dentro de una estructura, escriba una rutina que devuelva un puntero a la estructura.

struct s 
{ 
    ... 
    int a; 
    … 
}; 

struct s *get_s_ptr(int *a_ptr) 
{ 
    // implement this. 
} 

La respuesta es:

struct s* get_s_ptr(int *a_ptr) 
{ 
    return (struct s*)((char*)a_ptr - (int)&((struct s*)0)->a); 
} 
+0

Lo **¿Estás preguntando? –

+0

Lo siento, tenía la pregunta en el título pero no en la publicación en sí. Está arreglado ahora. – Steve

Respuesta

12

¿Cómo funciona?

La ecuación fundamental aquí (toda la aritmética en bytes) es

address of struct member s->a == s + byte offset of a 

Dado el tipo de s, un compilador, y una sola máquina de destino, que determina el desplazamiento de a — byte que es el lo mismo para cada estructura de tipo s.

Se le ha dado el lado izquierdo y su entrevistador le pidió que se recupere s. Puedes hacer esto obteniendo una nueva ecuación; restar el desplazamiento desde ambos lados de bytes:

address of struct member s->a - byte offset of a == s 

En el problema, estás dada la dirección de s->a, pero hay que averiguar el desplazamiento de bytes. Para ello se utiliza la ecuación original nuevo con s pusieron a cero:

address of struct member s->a where s is zero == zero + byte offset of a 
               == byte offset of a 

El lado izquierdo en C se construye de la siguiente manera

struct pointer s where s is zero       (struct s *)0 
struct member s->a where s is zero       ((struct s*)0)->a 
address of s->a where s is zero        &((struct s*)0)->a 

pasos finales:

  1. Para hacer que la aritmética sea legal C, este desplazamiento de bytes se convierte en un número entero.
  2. Para asegurarse de que la resta se realiza en unidades de bytes, a_ptr se convierte en char *.
  3. Para dar el resultado del tipo correcto, la diferencia se convierte en struct s *.

Adición: Como Eli Bendersky señala, se debe tratar de evitar situaciones en las que sería necesario este código. Casi siempre hay una mejor manera.

+0

muy agradable, ¡explicación! Gracias –

+3

que dio una explicación muy detallada de un pedazo de código completamente incorrecto, no estándar y generalmente * malo *. –

+1

@Eli: Buen punto. He editado mi respuesta. –

4

Puede utilizar offsetof macro.

struct s* get_s_ptr(int *a_ptr) 
{ 
    return (struct s*)((char*)a_ptr - offsetof(struct s,a)); 
} 

I's late. mi conexión a internet es lenta

5

La respuesta es: no es así. No funciona, incluso si parece "funcionar" a primera vista. La "respuesta" intenta desreferenciar un puntero nulo, lo que lleva a un comportamiento indefinido. Entonces, a menos que su idea de "trabajar" incluya un comportamiento indefinido, esa respuesta no funciona.

Hay más problemas con esa solución, además del intento de desactivar un puntero nulo (aunque eso solo es suficiente para arrojar esa "respuesta" al contenedor de basura). Otro problema es que el resultado de (struct s*) 0 es un puntero nulo del tipo struct s *. El lenguaje no garantiza el valor físico real de un puntero nulo. Si podría ser fácilmente algo así como 0xBAADFOOD, que de inmediato arruinaría la funcionalidad prevista de la "respuesta".

La correcta aplicación de la técnica implícita implicaría la norma offsetof macro (Ya se ha sugerido en la respuesta de Nyan, pero voy a repetir una vez más)

struct s* get_s_ptr(int *a_ptr) 
{ 
    return (struct s*) ((char *) a_ptr - offsetof(struct s, a)); 
} 
+0

Estoy de acuerdo con el puntero 'NULL'. Teóricamente podría ser distinto de cero, esto es un problema. Sin embargo, NO hay desreferenciación de este puntero. Si observas la definición de 'offsetof', verás exactamente lo mismo. Es decir, escribir & (pObj-> a) NO desreferencia nada. Porque el resultado de la expresión es la dirección. Es solo una aritmética – valdo

+1

& (pObj-> a) es igual que & ((* pObj) .a). Por lo tanto, desreferencia al puntero NULL si pObj es NULL. – Nyan

+0

Lo intenté aquí: http://codepad.org/PtLv8XN7. ((struct s *) 0) -> a conduce a una falla seg mientras que & ((struct s *) 0) -> a conduce al desplazamiento correcto. ¿Alguna idea? De todos modos, offsetof() es probablemente la mejor manera de hacerlo. – Steve

1

que esto sería útil,

/* offsetof example */ 
#include <stdio.h> 
#include <stddef.h> 

struct mystruct { 
    char singlechar; 
    char arraymember[10]; 
    char anotherchar; 
}; 

int main() 
{ 
    printf ("offsetof(mystruct,singlechar) is %d\n",offsetof(mystruct,singlechar)); 
    printf ("offsetof(mystruct,arraymember) is %d\n",offsetof(mystruct,arraymember)); 
    printf ("offsetof(mystruct,anotherchar) is %d\n",offsetof(mystruct,anotherchar)); 

    return 0; 
} 

salida:

offsetof(mystruct,singlechar) is 0 
offsetof(mystruct,arraymember) is 1 
offsetof(mystruct,anotherchar) is 11 

Así, en su caso,

return (struct s*) ((char *) a_ptr - offsetof(struct s, a)); 
  • fundido aptr a char *
  • restar el desplazamiento de a WRT a struct s
  • fundido a struct s*
  • retorno del resultant ptr
Cuestiones relacionadas