2009-09-21 10 views
7

Jest to makro offsetof w C/C++, które pozwala uzyskać przesunięcie adresu elementu w strukturze POD. Na przykład z C FAQ:Legit Zastosowania przesunięcia makro w C/C++

struct foo { 
int a; 
int b; 
}; 

struct foo; 

/* Set the b member of foo indirectly */ 
*(int *)((char *)foo + offsetof(b)) = 0xDEADBEEF; 

Teraz to po prostu wydaje mi się zła i nie widzę wiele legit zastosowania tego makra.

Jeden legit przykład widziałem jest jego stosowanie w container_of makro w Linux Kernel uzyskania adresu obiektu osadzonego struktury macierzystej:

/* get the address of the cmos device struct in which the cdev 
    structure which inode points to is embedded */ 
struct cmos_dev *cmos_devp = 
    container_of(inode->i_cdev, struct cmos_dev, cdev); 

Jakie inne legalne zastosowania są tam dla tego makra? Kiedy powinieneś użyć , a nie użyć tego makra?

EDYTOWANIE Do tej pory this answer na inne pytanie dotyczące SO jest najlepsze, jakie widziałem do tej pory.

+0

Twoje pierwsze użycie jest uzasadnione w C, ale w C++ wskaźniki-to-członka dość dużo usunąć potrzebę. –

+1

Oto niektóre inne pytanie o tym: http://stackoverflow.com/questions/400116/what-is-the-purpose-and-return-type-of-the-builtinoffsetof-operator –

Odpowiedz

4

Cóż ... W języku C jest to bardzo przydatne w każdym miejscu, w którym potrzebny jest kod do opisu struktury danych. Użyłem go np. do generowania graficznego GUI: s do ustawiania opcji.

To działało w następujący sposób: polecenie wymagające opcji definiuje lokalną strukturę z opcjami, a następnie opisuje tę strukturę do kodu, który generuje GUI, przy użyciu offsetof do wskazania, gdzie znajdują się pola. Używanie odsunięć zamiast adresów bezwzględnych pozwala kodowi GUI pracować z każdym wystąpieniem struktury, a nie tylko jednym.

To jest trochę trudne do szybkiego szkicowania w przykładzie (próbowałem), ale ponieważ komentarze wskazują, że przykład jest w porządku, spróbuję jeszcze raz.

Załóżmy, że mamy samodzielny moduł, zwany "poleceniem", który implementuje pewne działanie w aplikacji. To polecenie ma wiele opcji, które kontrolują jego ogólne zachowanie, które powinno być widoczne dla użytkownika za pośrednictwem graficznego interfejsu użytkownika. Na potrzeby tego przykładu załóżmy, że aplikacja jest menedżerem plików, a komendą może być np. "Kopiuj".

Chodzi o to, że kod kopiowania znajduje się w jednym pliku C, a kod GUI w innym, a kod GUI nie musi być zakodowany na sztywno, aby "obsługiwać" opcje polecenia kopiowania. Zamiast tego, możemy zdefiniować opcje w pliku kopii, tak jak poniżej:

struct copy_options 
{ 
    unsigned int buffer_size;  /* Number of bytes to read/write at a time. */ 
    unsigned int copy_attributes; /* Attempt to copy attributes. */ 
    /* more, omitted */ 
}; 

static struct copy_options options; /* Actual instance holding current values. */ 

Następnie polecenie copy rejestruje swoje ustawienia konfiguracji z modułem graficznym:

void copy_register_options(GUIModule *gui) 
{ 
    gui_command_begin(gui, "Copy"); 
    gui_command_add_unsigned_int(gui, "Buffer size", offsetof(struct copy_options, buffer_size)); 
    gui_command_add_boolean(gui, "Copy attributes", offsetof(struct copy_options, copy_attributes)); 
    gui_command_end(gui); 
} 

Następnie, powiedzmy użytkownik prosi o ustaw opcje polecenia kopiowania. Możemy wtedy najpierw skopiować bieżące opcje, aby wspierać anulowanie i poprosić moduł GUI dla dialogowych kontroli gospodarstwa, zbudowany w czasie wykonywania, nadaje się do edycji opcji tej komendy to:

void copy_configure(GUIModule *gui) 
{ 
    struct copy_options edit = options; 

    /* Assume this opens a modal dialog, showing proper controls for editing the 
    * named command's options, at the address provided. The function returns 1 
    * if the user clicked "OK", 0 if the operation was cancelled. 
    */ 
    if(gui_config_dialog(gui, "Copy", &edit)) 
    { 
    /* GUI module changed values in here, make edit results new current. */ 
    options = edit; 
    } 
} 

oczywiście ten kod zakłada ustawienia mają być czystymi typami wartości, więc możemy skopiować strukturę za pomocą prostego przypisania struktury. Jeśli obsługiwaliśmy również ciągi dynamiczne, potrzebowalibyśmy funkcji do kopiowania. Jednak w przypadku danych konfiguracyjnych każdy ciąg najprawdopodobniej byłby najlepiej wyrażony w postaci statycznej wielkości tablicy char w strukturze, co byłoby w porządku.

Uwaga, jak fakt, że tylko moduł GUI wie, gdzie każda wartość życia wyrażone jako offset pozwala nam zapewnić funkcję dialogowego z tymczasowej kopii na stosu. Gdybyśmy zamiast tego skonfigurowali moduł GUI z bezpośrednimi wskaźnikami dla każdego pola, nie byłoby to możliwe, co byłoby znacznie mniej elastyczne.

+0

nie byłoby po prostu za pomocą wskaźnika również pozwalają na "pracę z każdym wystąpieniem struktury"? –

+0

@Robert: Ponieważ działałoby tylko z jednym; ten, któremu dałeś wskaźnik. Używając przesunięć, możesz zbudować GUI dla dwóch różnych instancji tej samej struktury, bez konieczności ponownego opisywania struktury do kodu budującego GUI. – unwind

+0

Po prostu nie otrzymuję tego z jakiegoś powodu - czy możesz dodać krótki przykład kodu do swojego posta? –

1

offsetof jest stosunkowo często używany do programowania sterownika urządzenia, gdzie zwykle trzeba pisać w czystym C, ale czasami trzeba trochę „inne” możliwości. Rozważ, że masz funkcję zwrotną, która dostaje wskaźnik do jakiejś struktury. Teraz ta sama struktura jest członkiem innej, większej "zewnętrznej" struktury. z „offsetof” masz możliwość zmiany członków „zewnętrznej” strukturze, gdy masz dostęp tylko do „wewnętrznego” członka.

coś takiego:

struct A 
{ 
int a1; 
int a2; 
}; 

struct B 
{ 
int b1; 
int b2; 
A a; 
}; 

void some_API_callback_func(A * a) 
{ 
//here you do offsetof 
//to get access to B members 
} 

Oczywiście jest to niebezpieczne, jeśli masz możliwość, że struct A nie jest używany jako część struct B. Ale w wielu miejscach, w których ramy „some_API_callback_func” jest dobrze udokumentowana to działa dobrze.

+0

Dzięki, ale to jest dokładnie to, co już opisałem w odniesieniu do makra container_of w jądrze Linux. –

2

zasadzie cokolwiek chcesz zrobić ze wskaźnika do elementu (T::*) w C++ jest dobrym kandydatem do wykorzystania offsetof w C. Z tego powodu offsetof jest znacznie rzadsze w C++.

Teraz to jest oczywiście nieco okrągły, więc oto kilka przykładów:

  • pseudonazw funkcje sortowania dla elemencie. qsort używa wywołania zwrotnego, które nie jest idealne. Często wystarczy sortować według naturalnej kolejności jednego elementu, np. trzeci int w strukturze. Hipotetyczna qsort_int mógł zaakceptować offsetof argument dla tego celu.
  • Podobnie, jest możliwe, aby napisać makro extract taki sposób, że można powiedzieć int out[10]; extract(int, &MyFoo[0], &MyFoo[10], out, offsetof(struct Foo, Bar));
5

Jednym ze sposobów użyłem go w systemach wbudowanych jest gdzie mam struct która reprezentuje układ nieulotna pamięć (np. EEPROM), ale tam, gdzie nie chcę tworzyć instancji tej struktury w pamięci RAM. Możesz użyć różnych fajnych makrowych sztuczek, aby umożliwić odczyt i zapis konkretnych pól z EEPROM, gdzie offset wykonuje pracę obliczania adresu pola w strukturze.

Jeśli chodzi o "zło", trzeba pamiętać, że wiele rzeczy, które tradycyjnie odbywały się w programowaniu w języku C, szczególnie na platformach o ograniczonych zasobach, teraz wygląda na złego hackery, gdy ogląda się go z luksusowego otoczenia nowoczesnej informatyki. .

5

Jeden uzasadnione stosowanie offsetof() jest określenie wyrównania typu:

#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

może być trochę niski poziom potrzebować wyrównanie obiektu, ale w każdym razie Pomyślę o tym uzasadnione użycie.