2010-06-20 13 views
8

Oto pytanie do wywiadu, które zobaczyłem na jakimś forum. Próbowałem dowiedzieć się, jak to działa, ale nie całkiem rozumiem. Czy ktoś mógłby wyjaśnić, jak to działa?Podając wskaźnik do elementu a wewnątrz struktury, napisz procedurę, która zwraca wskaźnik do struct

P: Biorąc wskaźnik do elementu a w strukturze, napisz procedurę, która zwraca wskaźnik do struktury.

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

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

Odpowiedź brzmi:

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

Co **czy ty pytasz? –

+0

Przepraszam, miałem pytanie w tytule, ale nie sam post. Naprawiono to teraz. – Steve

Odpowiedz

12

Jak to działa?

Podstawowym równanie tutaj (wszystkie działania arytmetyczne w bajtach) jest

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

Biorąc pod uwagę rodzaj s pojedynczy kompilator, a pojedynczy komputer docelowy, że określona offset bajtowy a — jest to to samo dla każdej struktury typu s.

Masz lewą stronę, a Twój ankieter poprosił Cię o odzyskanie s. Możesz to zrobić, otrzymując nowe równanie; odjąć offset bajtowy z obu stron:

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

W problemu, jesteś daną adres s->a, ale trzeba dowiedzieć się bajt offset. Aby to zrobić, należy użyć oryginalnego równania ponownie z s zestaw do zera:

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

boczna lewa w C zbudowany jest następująco

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 

czynności końcowych:

  1. Aby uczynić arytmetyczną legalną C, offset bajtowy jest rzutowany na liczbę całkowitą.
  2. Aby się upewnić, że odejmowanie odbywa się w jednostkach bajtów, a_ptr jest rzutowane na char *.
  3. Aby dać wynik odpowiedniego typu, różnica jest rzutowana na struct s *.

Uzupełnienie: Jak Eli Bendersky zwraca uwagę, należy starać się unikać sytuacji, w których ten kod byłyby konieczne. Zawsze jest lepszy sposób.

+0

Naprawdę miło, wyjaśnienie! dziękuje –

+3

dałeś ładnie szczegółowe wyjaśnienie całkowicie błędnego, niestandardowego i ogólnie * złego * kawałka kodu. –

+1

@ Eli: Dobra uwaga. Edytowałem swoją odpowiedź. –

4

Można użyć offsetof makro.

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

Jestem spóźniony. moje połączenie internetowe jest wolne.

5

Odpowiedź brzmi: tak nie jest. To nie działa, nawet jeśli może się wydawać, że "działa" na pierwszy rzut oka. "Odpowiedź" sprawia, że ​​próbujesz wyłuskać wskaźnik zerowy, co prowadzi do niezdefiniowanego zachowania. Tak więc, jeśli twój pomysł "pracy" nie obejmuje niezdefiniowanego zachowania, ta odpowiedź nie działa.

Istnieje więcej problemów z tym rozwiązaniem, poza próbą wyłączenia wskaźnika zerowego (chociaż samo to wystarczy, aby wyrzucić tę "odpowiedź" do kosza na śmieci). Innym problemem jest to, że wynik (struct s*) 0 jest zerowym wskaźnikiem typu struct s *. Język nie gwarantuje rzeczywistej wartości fizycznej wskaźnika pustego. Jeśli z łatwością mógłby być czymś w rodzaju 0xBAADFOOD, co natychmiast zepsułoby zamierzoną funkcjonalność "odpowiedzi".

Właściwa realizacja domniemanych techniki wiązałoby średnia offsetof makro (już sugerowane w odpowiedzi Nyan, ale ja to powtórzyć jeszcze raz)

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

Zgadzam się na temat wskaźnika "NULL". Teoretycznie może być niezerowa, to śrubuje rzeczy. Jednak NIE ma dereferencji tego wskaźnika. Jeśli spojrzysz na definicję "przesunięcia" - zobaczysz dokładnie to samo. Oznacza to, że pisanie i (pObj-> a) NIE dereferencji niczego. Ponieważ wynikiem wyrażenia jest adres. To tylko arytmetyka – valdo

+1

& (pObj-> a) jest taka sama jak & ((* pObj) .a). Tak więc wyłuskuje NULL wskaźnik, jeśli pObj ma wartość NULL. – Nyan

+0

Próbowałem go tutaj: http://codepad.org/PtLv8XN7. ((struct s *) 0) -> prowadzi do błędu seg, gdy & ((struct s *) 0) -> a prowadzi do poprawnego przesunięcia. jakieś pomysły? Niezależnie od tego, offsetof() jest prawdopodobnie najlepszym sposobem na zrobienie tego. – Steve

1

że to byłoby pomocne,

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

wyjściowa:

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

Więc w twoim przypadku,

return (struct s*) ((char *) a_ptr - offsetof(struct s, a)); 
  • cast aptr char *
  • odjąć offset a wrt do struct s
  • oddanych struct s*
  • zamian resultant ptr
Powiązane problemy