2008-11-04 13 views
23

Próbuję zrozumieć zasady ścisłego aliasingu, ponieważ mają zastosowanie do wskaźnika znaku.Kiedy jest char * bezpieczny dla aliasingu ze ścisłym wskaźnikiem?

Here to stwierdził:

To jest zawsze zakładać, że char * może odnosić się do aliasu dowolnego obiektu.

Ok, więc w kontekście kodeksu gniazda, mogę to zrobić:

struct SocketMsg 
{ 
    int a; 
    int b; 
}; 

int main(int argc, char** argv) 
{ 
    // Some code... 
    SocketMsg msgToSend; 
    msgToSend.a = 0; 
    msgToSend.b = 1; 
    send(socket, (char*)(&msgToSend), sizeof(msgToSend); 
}; 

Ale potem jest to stwierdzenie

Odwrotna nie jest prawdą. Przesyłanie znaku * do wskaźnika dowolnego typu, innego niż znak * i dereferencja, zwykle narusza zasadę ścisłego aliasingu.

Czy to oznacza, że ​​kiedy recv tablicy char, nie mogę reinterpretacji obsady do struct kiedy wiem strukturę wiadomości:

struct SocketMsgToRecv 
{ 
    int a; 
    int b; 
}; 

int main() 
{ 
    SocketMsgToRecv* pointerToMsg; 
    char msgBuff[100]; 
    ... 
    recv(socket, msgBuff, 100); 
    // Ommiting make sure we have a complete message from the stream 
    // but lets assume msgBuff[0] has a complete msg, and lets interpret the msg 

    // SAFE!?!?!? 
    pointerToMsg = &msgBuff[0]; 

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b); 
} 

Będzie to drugi przykład nie działa, ponieważ typem podstawowym jest tablica znaków i wrzucam ją do struktury? Jak radzisz sobie z tą sytuacją w ściśle sprecyzowanym świecie?

+0

Czy drugi fragment kodu nie wymaga jawnie casr? Czy włączono wszystkie ostrzeżenia? –

Odpowiedz

6

Prawidłowo, drugi przykład jest niezgodny z regułami ścisłego aliasingu, więc jeśli skompilujesz z flagą -fstrict-aliasing, istnieje szansa, że ​​otrzymasz niepoprawny kod obiektu. W pełni poprawne rozwiązanie byłoby użyć unii tutaj:

union 
{ 
    SocketMsgToRecv msg; 
    char msgBuff[100]; 
}; 

recv(socket, msgBuff, 100); 

printf("Got Msg: a: %i, b: %i", msg.a, msg.b); 
+1

Czy jest to zgodne ze standardem lub tylko kompilatorem, pozwalającym uciec z pisania do jednego członka i czytając od innego? –

+9

Związek jest zupełnie niepotrzebny. Wystarczy przekazać wskaźnik do struktury (rzutowanie na 'char *') na 'recv'. –

+0

Zwróć uwagę, że '-fstrict-aliasing' jest domyślnie włączone w' -O2' i wyżej w gcc –

7

Re @Adam Rosenfield: Związek będzie osiągnąć wyrównanie dopóki dostawcą char * zaczęło się robić coś podobnego.

Może warto się wycofać i dowiedzieć się, o co w tym wszystkim chodzi.

Podstawą reguły aliasingu jest fakt, że kompilatorzy mogą umieszczać wartości różnych typów prostych na różnych granicach pamięci, aby poprawić dostęp i że sprzęt może w niektórych przypadkach wymagać takiego wyrównania, aby w ogóle móc użyć wskaźnika. Może to również pojawić się w strukturach, w których występuje wiele elementów o różnych rozmiarach. Strukturę można rozpocząć na dobrej granicy. Ponadto kompilator może nadal wprowadzać luźne brania we wnętrzu struktury, aby osiągnąć prawidłowe wyrównanie elementów strukturalnych, które tego wymagają.

Biorąc pod uwagę, że kompilatory często mają opcje kontrolowania tego, jak to wszystko jest obsługiwane, czy nie, można zauważyć, że istnieje wiele sposobów na niespodzianki. Jest to szczególnie ważne, aby być świadomym, kiedy przekazywane są wskaźniki do struktur (rzutowane jako char * lub nie) do bibliotek, które zostały skompilowane, aby oczekiwać różnych konwencji wyrównania.

Co z char *?

Domniemanie o char * jest tym sizeof (char) == 1 (w stosunku do rozmiarów wszystkich innych sporej wielkości danych) i że wskaźniki char * nie mają żadnego wymogu wyrównania. Tak więc prawdziwy char * może zawsze być bezpiecznie przekazywany i używany z powodzeniem bez troski o wyrównanie, a to odnosi się do dowolnego elementu tablicy char [], wykonująC++ i - na wskaźnikach i tak dalej. (Dziwne, void * to nie to samo.)

Teraz powinieneś być w stanie zobaczyć, jak przesłać jakieś dane struktury do tablicy char [], która nie została odpowiednio wyrównana, próba powrotu do wskaźnika, który wymaga wyrównania (osi), może być poważny problem.

Jeśli tworzysz połączenie tablicy char [] i struktury, najbardziej wymagające wyrównanie (to jest struktury struct) będzie honorowane przez kompilator. To zadziała, jeśli dostawca i konsument efektywnie użyją pasujących związków, dzięki czemu rzutowanie struktury * na znak * i powrót działa dobrze.

W tym przypadku, mam nadzieję, że dane zostały utworzone w podobnym związku, zanim wskaźnik do niego został rzucony na char * lub został przeniesiony w inny sposób jako tablica sizeof (char) bajtów. Ważne jest również, aby upewnić się, że wszystkie opcje kompilatora są kompatybilne między bibliotekami, z których się korzysta, i własnym kodem.

+0

są wszystkie 3 'char',' signed char', 'unsigned char' OK dla aliasingu? oraz z dowolną kombinacją kwalifikacji CV? –

+2

Reguły aliasingu nie mają nic wspólnego z wyrównaniem. Zgodnie z uzasadnieniem C89, biorąc pod uwagę globalne deklaracje typu 'int i; float * fp; ', celem jest umożliwienie kompilatorom utrzymanie' i' w rejestrze poprzez dostęp do '* fp'. Pomysł polegał na tym, że kompilator nie powinien pesymistycznie zakładać, że zapis do '* fp' może zmienić' i' *, gdy nie ma powodu oczekiwać *, że '* fp' wskaże coś, co nie było'. float' *. Nie sądzę, że reguła miała kiedykolwiek pozwolić kompilatorom zignorować przypadki, w których aliasing jest oczywisty (wpisanie adresu obiektu powinno dać kompilatorowi silny trop ... – supercat

+0

... że przedmiot, o którym mowa, wkrótce będzie dostępny przez wskaźnik, a rzutowanie 'int *' na 'float *' powinno dać kompilatorowi silną wskazówkę, że 'int' może zostać zmodyfikowane poprzez zapis do' float * ', ale gcc nie odczuwa już żadnego obowiązku Zauważ takie rzeczy: – supercat

Powiązane problemy