2016-05-24 7 views
10

załóżmy mam struct i wyodrębnić przesunięcie do członka:Jak używać `offsetof` do dostępu do pola w standardowy sposób zgodny?

struct A { 
    int x; 
}; 

size_t xoff = offsetof(A, x); 

jak mogę, biorąc pod uwagę wskaźnik do struct A wyodrębnić element w standardzie zgodnym sposób? Zakładając oczywiście, że mamy poprawną struct A* i prawidłowe przesunięcie. Jedna próba byłoby zrobić coś takiego:

int getint(struct A* base, size_t off) { 
    return *(int*)((char*)base + off); 
} 

Który prawdopodobnie będzie działać, ale należy pamiętać, że na przykład arytmetyka wskaźnik tylko wydają się być zdefiniowany w normie, czy wskaźniki są wskaźnikami o tej samej tablicy (lub jeden obok koniec), nie musi tak być. Technicznie rzecz biorąc, konstrukcja ta zdaje się opierać na niezdefiniowanym zachowaniu.

Innym rozwiązaniem byłoby

int getint(struct A* base, size_t off) { 
    return *(int*)((uintptr_t)base + off); 
} 

które również prawdopodobnie będzie działać, ale należy pamiętać, że nie jest wymagane intptr_t istnieć io ile wiem, arytmetyka na intptr_t nie potrzebuje, uzyskując prawidłowy wynik (dla przykład Przypominam, że niektóre procesory mają możliwość obsługi adresów wyrównanych bajtowo, co sugerowałoby, że intptr_t wzrasta w krokach po 8 dla każdego char w tablicy).

Wygląda na to, że w standardzie jest coś zapomnianego (lub coś, co przeoczyłem).

+1

jestem całkiem pewny aliasing do '*' char i wskaźników, które wskazują na ten sam obiekt (niekoniecznie tablica) jest ważny. Oczekiwanie na autorytatywną odpowiedź. – Quentin

+1

'' (char *) base' może być używane do poruszania się w dowolnym miejscu wewnątrz 'bazy' (i jednego za końcem). Każdy obiekt zachowuje się jak tablica o rozmiarze 1. –

+0

'return * (int *) ((char *) base + off);' może łatwo zawieść, ponieważ dostęp 'int' może być niewyrównany. Na przykład. dostęp 'int' może powodować błąd magistrali na nieparzystym adresie. OP OTOH powiedział "Zakładając ... mamy poprawną strukturę A * i poprawne przesunięcie" – chux

Odpowiedz

3

Per na C Standard, 7,19 Wspólne definicje <stddef.h> ustęp 3 offsetof() jest zdefiniowany jako:

Makra są

NULL 

który rozszerza się do realizacji zdefiniowanej zerowym wskaźnikiem stałym; i

offsetof(*type*, *member-designator*) 

który rozszerza się w stałej całkowitej ekspresji, który jest typu size_t, z których wartość jest przesunięcie w bajtach członu struktury (oznaczona przez członków oznacznikiem) od początek jego struktury (oznaczony przez typ).

Więc offsetoff() zwraca przesunięcie w bajtach.

I 6.2.6.1 Ogólne ustęp 4 stanowi:

Wartości przechowywane w przedmiotach innych niż bitowe pole dowolnego innego typu obiektu składają n x CHAR_BIT bitów, gdzie n to rozmiar obiektu tego typu w bajtach.

Ponieważ CHAR_BIT definiuje się jako liczbę bitów w char, A char jest bajt.

Tak, to prawda, zgodnie z normą:

int getint(struct A* base, size_t off) { 
    return *(int*)((char*)base + off); 
} 

To konwertuje base do char * i dodaje off bajtów adresu. Jeśli off jest wynikiem offsetof(A, x); uzyskany adres jest adresem x ciągu structure A że base wskazuje.

Twój Drugi przykład:

int getint(struct A* base, size_t off) { 
    return *(int*)((intptr_t)base + off); 
} 

zależy od wyniku dodania podpisanej wartości intptr_t z unsigned wartość size_t jest niepodpisany.

+4

Cytowane części są zupełnie nieistotne. Odpowiednimi częściami standardu byłyby te w 6.5 dotyczące aliasingu wskaźnika, a może części dotyczące arytmetyki wskaźnika. Nie widzę, jak mógłby się nie udać drugi przykład. 'intptr_t' jest typem liczby całkowitej bez znaku, a nie typem wskaźnika. Nie wykonuje arytmetyki wskaźnika, więc twoje założenia są nieprawidłowe. – Lundin

+0

@Lundin - Tak, masz rację. Z jakiegoś powodu przeczytałem 'intptr_t' jako' int * '. Poprawiając odpowiedź teraz, ale najpierw muszę pomyśleć o tym, co się stanie, gdy 'intptr_t' jest podpisany. –

+0

Zgadzam się z @Lundin, z wyjątkiem 'intptr_t' jest znakiem typu integer kontra' uintptr_t' – chux

0

Powodem średnia (6.5.6) pozwala jedynie wskaźnik arytmetycznych na macierzach, że kodowanym może mieć bajty wypełniające aby zaspokoić wymagania wyrównania. Czyniąc zatem wskaźnik arytmetyczny wewnątrz struktury jest to rzeczywiście formalnie niezdefiniowane zachowanie.

W praktyce będzie działać tak długo, jak wiesz co robisz. base + off nie może zawieść, ponieważ wiemy, że tam są poprawne dane i nie są one źle ustawione, biorąc pod uwagę, że są dostępne prawidłowo.

Zatem kod (intptr_t)base + off jest znacznie lepszy, ponieważ nie ma już arytmetyki wskaźnika, ale zwykła arytmetyczna liczba całkowita. Ponieważ intptr_t jest liczbą całkowitą, nie jest wskaźnikiem.

Jak podkreślono w komentarzu, ten typ nie jest gwarantowana istnieć, to jest opcjonalne zgodnie 7.20.1.4/1. Przypuszczam, że dla maksymalnej przenośności, można przejść do innych typów, które sągwarantowanych istnieć, takich jak intmax_t lub ptrdiff_t. Można jednak argumentować, że kompilator C99/C11 bez wsparcia dla intptr_t jest w ogóle przydatny.

(Istnieje niewielka typu problem tutaj, a mianowicie, że intptr_t jest podpisana typu, niekoniecznie zgodne z size_t. Można dostać ukryte problemy typu promocji. Bezpieczniej jest używać uintptr_t jeśli to możliwe).

Następne pytanie brzmi: czy *(int*)((intptr_t)base + off) jest dobrze zdefiniowanym zachowaniem. Część standardu dotycząca konwersji wskaźnika (6.3.2.3) mówi, że:

Dowolny typ wskaźnika może być konwertowany na typ całkowity. Z wyjątkiem wcześniej podanego wynik jest zdefiniowany przez implementację. Jeśli wynik nie może być reprezentowany w typie całkowitym, zachowanie jest nieokreślone: ​​ . Wynik nie musi znajdować się w zakresie wartości dowolnego typu liczbowego .

W tym konkretnym przypadku, wiemy że mamy poprawnie wyrównane int tam, więc to jest w porządku.

(nie wierzę, że wszelkie obawy wskaźnik aliasingu stosuje się albo. Przynajmniej kompilacji z gcc -O3 -fstrict-aliasing -Wstrict-aliasing=2 nie złamać kod.)

+2

"Ponieważ intptr_t jest liczbą całkowitą, ... jest gwarantem istnienia ... kompilator (C99/C11)" -> "' intptr_t' ... 'uintptr_t' Te typy to _optional_." §7.20.1.4 1 – chux

+0

@chux Ach, nauczyłem się czegoś nowego! :) Będzie edytować odpowiedź, dziękuję. – Lundin

Powiązane problemy