2016-01-22 13 views
8

Rozważmy następujący przykład program:Konwersja wskaźnik do struktury do jej pierwszego elementu

#include <stdio.h> 

struct base { 
    int a, b; 
}; 

struct embedded { 
    struct base base; 
    int c, d; 
}; 

struct pointed { 
    struct base* base; 
    int c, d; 
}; 

static void base_print(struct base* x) { 
    printf("a: %d, b: %d\n", x->a, x->b); 
} 

static void tobase_embedded(void* object) { 
    base_print(object); // no cast needed, suitably converted into first member. 
} 

static void tobase_pointed(void* object) { 
    struct base* x = *(struct base**) object; // need this cast? 
    base_print(x); 
} 

int main(void) { 
    struct embedded em = {{4, 2}}; 
    struct pointed pt = {&em.base}; 
    tobase_embedded(&em); 
    tobase_pointed(&pt); 
    return 0; 
} 

skompilowany z:

$ gcc -std=c99 -O2 -Wall -Werror -pedantic -o main main.c 

Oczekiwany wynik jest:

$ ./main 
a: 4, b: 2 
a: 4, b: 2 

C99 standard mówi o pierwszym członku struktury:

C99 6.7.2.1 (13): Wskaźnik do obiektu konstrukcji, odpowiednio przekształcony, wskazuje na swój początkowy element ... i odwrotnie. Możliwe jest wypełnienie bez nazwy wewnątrz obiektu struktury, ale nie na jego początku.

W programie przykład wskaźnik do struct embedded jest przekształcany do wskaźnika do struct base (poprzez void*) bez konieczności wyraźnego obsady.

Co, jeśli zamiast tego pierwszy element jest wskaźnikiem bazowym, jak w przypadku struct pointed? Nie jestem pewien co do obsady w ramach tobase_pointed. Bez wydrukowanych rzutów śmieci, ale bez ostrzeżeń/błędów kompilacji. Wraz z rzutem drukowane są prawidłowe wartości dla base.a i base.b, ale to naprawdę niewiele znaczy, jeśli istnieje niezdefiniowane zachowanie.

Czy obsada do konwersji struct pointed na pierwszego członka jest poprawna? struct base*?

+0

Pierwszym elementem 'struct pointed' jest wskaźnik do' struct base'. Będzie to wymagało wyłomu "void * object". Ale nie można usunąć dereferencji 'void *' bez informowania kompilatora o tym, jak usunąć wskaźnik, stąd potrzeba rzutowania. – alvits

Odpowiedz

2

Kod nie tylko rzuca, ale także usuwa zaznaczenie wskaźnika do wskaźnika na podstawę struktury. Jest to konieczne do uzyskania wskaźnika do bazy w pierwszej kolejności.

To co się dzieje w kodzie, jeśli funkcja tobase_pointed został usunięty:

struct pointed pt = {&em.base}; 
void* object = &pt;     //pass to the function 
struct base** bs = object;   //the cast in the function 
assert(bs == (struct base**)&pt) ; //bs points to the struct pointed 
assert(bs == &(pt.base)) ;   //bs also points to the initial member struct base* base 
struct base* b = *bs ;    //the dereference in the function 
base_print(x); 

bs jest wskaźnik, który został odpowiednio przekształcone do punktu początkowego do członka. Twój kod jest poprawny.

+1

Dzięki temu krok po kroku robi się krystalicznie czysty. – Adam

1

Ta obsada jest uzasadniona i jest potrzebna, ponieważ chcesz przekonwertować wskaźnik na wskaźnik na wskaźnik. Jeśli nie rzucasz, dereferencja będzie niepoprawna.

Innymi słowy, twój base* ma ten sam adres co obiekt pt. Możesz więc uzyskać do niego dostęp za pomocą wskaźnika do pt. Ale musisz to usunąć.

Powiązane problemy