2010-06-20 5 views
8

Voici une question d'entrevue que j'ai vue sur un forum. J'ai essayé de comprendre comment cela fonctionne mais je ne comprends pas tout à fait. Quelqu'un pourrait-il expliquer comment cela fonctionne? Q: Étant donné un pointeur sur le membre a dans une structure, écrivez une routine qui renvoie un pointeur vers la structure.Étant donné un pointeur vers le membre a dans une structure, écrivez une routine qui renvoie un pointeur vers la structure

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

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

La réponse est:

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

Qu'est-ce que ** ** demandez-vous? –

+0

Désolé, j'ai eu la question dans le titre mais pas dans le message lui-même. C'est corrigé maintenant. – Steve

Répondre

12

Comment ça marche?

L'équation fondamentale ici (toute l'arithmétique en octets) est

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

Compte tenu du type de s, un seul compilateur, et une seule machine cible, ils ont déterminé l'octet de décalage de a — il est la même pour chaque structure de type s.

Vous avez le côté gauche et votre enquêteur vous a demandé de récupérer s. Vous pouvez le faire en obtenant une nouvelle équation; soustraire l'octet décalé des deux côtés:

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

Dans le problème, vous êtes donné l'adresse de s->a, mais vous devez comprendre l'octet de décalage. Pour ce faire, vous utilisez l'équation d'origine à nouveau avec s set à zéro:

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

Le côté gauche C est construite comme suit

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 

étapes finales:

  1. Pour rendre l'arithmétique C légale, ce décalage d'octets est converti en entier.
  2. Pour vous assurer que la soustraction est effectuée en unités d'octets, a_ptr est converti en char *. Pour donner le résultat correct, la différence est portée à .

Addendum: Comme le souligne Eli Bendersky, vous devriez essayer d'éviter les situations où ce code serait nécessaire. Il y a presque toujours un meilleur moyen.

+0

vraiment bien, explication! merci –

+3

vous avez donné une explication bien détaillée d'un morceau de code tout à fait faux, non standard et généralement * mauvais *. –

+1

@Eli: Bon point. J'ai édité ma réponse. –

4

Vous pouvez utiliser offsetof macro.

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

I est en retard. ma connexion internet est lente.

5

La réponse est: ce n'est pas le cas. Cela ne fonctionne pas, même si cela peut sembler "fonctionner" à première vue. La "réponse" fait une tentative de déréférencer un pointeur nul, ce qui conduit à un comportement indéfini. Donc, à moins que votre idée de "travail" inclue un comportement indéfini, cette réponse ne fonctionne pas.

Il y a plus de problèmes avec cette solution, en plus de la tentative de derefercence un null pointeur (bien que cela seul est parfaitement suffisant pour jeter cette "réponse" à la poubelle). Un autre problème est que le résultat de (struct s*) 0 est un pointeur nul de type struct s *. Le langage ne garantit pas la valeur physique réelle d'un pointeur nul. Si pourrait facilement être quelque chose comme 0xBAADFOOD, ce qui serait immédiatement bousiller la fonctionnalité prévue de la "réponse".

La mise en œuvre de la technique implicite impliquerait la norme offsetof macro (déjà suggéré dans la réponse de Nyan, mais je vais le répéter une fois de plus)

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

D'accord sur le pointeur "NULL". Théoriquement, il pourrait être non-nul, cela vis des choses. Cependant, il n'y a pas de déréférencement de ce pointeur. Si vous regardez la définition de 'offsetof' - vous verrez exactement la même chose. C'est-à-dire, écrire & (pObj-> a) ne déréférence rien. Parce que le résultat de l'expression est l'adresse. C'est juste un arithmétique – valdo

+1

& (pObj-> a) est même que & ((* pObj) .a). Donc, il déréférence NULL pointeur si pObj est NULL. – Nyan

+0

Je l'ai essayé ici: http://codepad.org/PtLv8XN7. ((struct s *) 0) -> a conduit à une erreur seg pendant que & ((struct s *) 0) -> a conduit au décalage correct. Des pensées? Quoi qu'il en soit, offsetof() est probablement le meilleur moyen de le faire. – Steve

1

pensé que ce serait utile,

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

sortie:

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

Donc, dans votre cas,

return (struct s*) ((char *) a_ptr - offsetof(struct s, a)); 
  • fonte aptr char *
  • Soustraire le décalage de a WRT à struct s
  • CAST pour struct s*
  • retour le resultant ptr
Questions connexes