2011-02-02 17 views
8

Chcę pobrać część danych wewnątrz niektórych struktur C, aby częściowo je przekształcić/deserializować, zapisując bajty z pamięci na dysk i viceversa.sizeof() część struktury C - sortowanie

Struktury nie są znane z góry, są budowane dynamicznie za pomocą mojego własnego generatora kodu C (jak również kodu, który je serializuje). Pola serializowalne zostaną umieszczone na początku struktury.

Powiedzmy, że mają struct z 4 polami, a pierwsze dwa mają być szeregowane:

typedef struct { 
    int8_t x1; 
    int32_t x2; /* 1 + 4 = 5 bytes (if packed) */ 
    int8_t y1; 
    int32_t y2; /* 1 + 4 +1 + 4 = 10 bytes (if packed) */ 
} st; 

mam zamiar chwycić wskaźnik do zmiennej struct i zapisu/odczytu na n bajtów, które obejmują te, dwa pierwsze pola (x1, x2). Nie sądzę, żebym musiał się martwić o wyrównanie/pakowanie, ponieważ nie zamierzam serializacji, aby przetrwać różne kompilacje (oczekuje się, że tylko jeden plik wykonywalny będzie czytać/zapisywać dane). A ponieważ jestem ukierunkowany na szeroki zakres kompilatorów-architektur, nie chcę umieszczać założeń na temat alignment-packing lub specyficznych sztuczek kompilatora.

Następnie muszę policzyć bajty. I nie mogę po prostu zrobić sizeof(st.x1)+sizeof(st.x2) z powodu dopełnienia-dopełnienia. Tak, mam zamiar odjąć wskazówek, od początku do pierwszej struktury „bez przetrwałego” pola:

st myst; 
int partsize = (char*)&myst.y1 - (char*)(&myst); 
printf("partial size=%d (total size=%d)\n",partsize,sizeof(myst)); 

To wydaje się działać. I może być umieszczony w makrze.

(Dla przypomnienia: próbowałem również napisać inne makro, które nie wymaga instancji struktury, coś w rodzaju this, ale nie wydaje się to możliwe tutaj - ale to nie ma znaczenia dla mnie).

Moje pytanie: czy to jest poprawne i bezpieczne? Czy widzisz jakąś pułapkę lub lepsze podejście?

Między innymi: czy standardowe C (i de facto kompilatory) zakładają, że pola structs leżą w pamięci w tej samej kolejności, w jakiej są zdefiniowane w źródle? Prawdopodobnie jest to głupie pytanie, ale chciałbym mieć pewność, ...

UPDATE: Niektóre wnioski z odpowiedzi i moje własne odkrycia:

  1. Wydaje się, że nie ma problemu z moim podejściem . W szczególności C dyktuje, że pola struct nigdy nie zmienią porządku.

  2. Można również (zgodnie z sugestią aswer) policzyć z ostatniego trwałego pola i dodać jego rozmiar: (char*)&myst.x2 + sizeof(&myst.x2) - (char*)(&myst). Byłoby to równoważne, poza tym, że nie zawierałby dopełnienia bajtów (jeśli są obecne) dla ostatniego pola. Bardzo mała przewaga - i bardzo mała wada, w byciu mniej prostą.

  3. Ale zaakceptowana odpowiedź, z offsetof, wydaje się być lepsza niż moja propozycja. Jest to wyraźna ekspresja i czysty czas kompilacji, nie wymaga instancji struktury. Dalej wydaje się być standardem, dostępnym w każdym kompilatorze. Jeśli nie potrzeba konstruować kompilacji i istnieje instancja dostępnej struktury (jak to jest w moim scenariuszu), oba rozwiązania są w zasadzie równoważne.

+0

Powiązane pytanie: http://stackoverflow.com/questions/2748995/c-struct-memory-layout – payne

+0

Usunąłem tag C++, ponieważ w pobliżu tego pytania nie ma C++. – Puppy

Odpowiedz

10

Czy obejrzałeś obiekt offsetof? Zwraca przesunięcie elementu od początku struktury. Tak więc offsetof (st, x2) zwraca przesunięcie x2 od początku struktury. Tak więc w twoim przykładzie offsetof (st, x2) + sizeof(st.x2) podajesz liczbę bajtów zserializowanych komponentów.

Jest to bardzo podobne do tego, co robisz teraz, po prostu zignorować wyściółkę po x2 i używać rzadko używany kawałek C

+0

+1: Pokonaj mnie do tego :) – Lars

+0

Świetna sugestia, nie wiedział (lub nie pamiętam?). Niestety, wydaje się, że nie jest wszechobecny. W szczególności MINGW gcc go nie ma. Ale zerkanie na jego definicję w innych kompilatorach ma wielką wartość. – leonbloy

+0

@leonbloy: 'offsetof()' jest standardem C, ale aby go uzyskać, musisz dodać ''. – caf

0

Być może zechcesz rzucić okiem na protobuf; wydaje się robić to, co chcesz w przenośny sposób.

5

C gwarantuje takie zachowanie. Ma na celu umożliwienie prymitywnego polimorfizmu. Rozważ:

struct X { 
    int a; 
}; 
struct Y { 
    int a; 
    int b; 
}; 
void foo(X* x) { 
    x->a = 10; 
}; 
Y y; 
foo((X*)&y); // Well defined behaviour- y.a = 10. 
3

Kompilator C może wstawić bajty wypełnienia w celu wyrównania, ale nie może zmienić kolejności zmiennych struct.

Sprytniejszym sposobem może być po prostu zdefiniowanie drugiej struktury dla celów sizeof(), która zawiera początkowe zmienne wybranej struktury. Kompilator zagwarantuje, że 2 kolumny, które mają te same zmienne w tej samej kolejności, zostaną rozmieszczone w ten sam sposób.

+0

+1 Dobra sugestia. Ale w moim scenariuszu - mnóstwo struktur z mnóstwem pól - to byłoby zbyt inwazyjne (jeśli zmienię definicję struktur, zagnieżdżenie) lub wprowadzę zbyt dużo bałaganu (jeśli utworzę bliźniaczą strukturę dla każdego oryginału). – leonbloy

0

zagłosuję za zasadą KISS. Napisz do elementu pliku po elemencie i nie masz gwarancji zależności od kompilatora.