Una macro “container_of” può mai essere strettamente conforms?

Una macro comunemente usata nel kernel di Linux (e in altri posti) è container_of , che è (fondamentalmente) definito come segue:

 #define container_of(ptr, type, member) (((type) *)((char *)(ptr) - offsetof((type), (member)))) 

Che sostanzialmente permette il recupero di una struttura “genitore” dato un puntatore a uno dei suoi membri:

 struct foo { char ch; int bar; }; ... struct foo f = ... int *ptr = &f.bar; // 'ptr' points to the 'bar' member of 'struct foo' inside 'f' struct foo *g = container_of(ptr, struct foo, bar); // now, 'g' should point to 'f', ie 'g == &f' 

Tuttavia, non è del tutto chiaro se la sottrazione contenuta in container_of sia considerata comportamento non definito.

Da un lato, poiché la bar all’interno di struct foo è solo un singolo intero, allora solo *ptr dovrebbe essere valido (come pure ptr + 1 ). Pertanto, il container_of produce effettivamente un’espressione come ptr - sizeof(int) , che è un comportamento non definito (anche senza dereferenziazione).

D’altra parte, §6.3.2.3 p.7 dello standard C afferma che la conversione di un puntatore in un tipo diverso e viceversa produce lo stesso puntatore. Pertanto, “spostare” un puntatore al centro di un object struct foo , quindi tornare all’inizio dovrebbe produrre il puntatore originale.

La preoccupazione principale è il fatto che le implementazioni sono autorizzate a verificare l’indicizzazione out-of-bounds in fase di esecuzione. La mia interpretazione di questo e del summenzionato requisito di equivalenza del puntatore è che i limiti devono essere preservati attraverso i cast del puntatore (questo include il decadimento del puntatore – altrimenti, come si può usare un puntatore per scorrere su un array?). Ergo, mentre ptr può essere solo un puntatore int , e né ptr - 1*(ptr + 1) sono validi, ptr dovrebbe avere ancora qualche nozione di essere nel mezzo di una struttura, in modo che (char *)ptr - offsetof(struct foo, bar) è valido (anche se il puntatore è uguale a ptr - 1 in pratica).

Infine, mi sono imbattuto nel fatto che se hai qualcosa del tipo:

 int arr[5][5] = ... int *p = &arr[0][0] + 5; int *q = &arr[1][0]; 

mentre è un comportamento indefinito a dereferenziamento p , il puntatore da solo è valido e richiede un confronto uguale a q (vedi questa domanda ). Ciò significa che p e q confrontano lo stesso, ma possono essere diversi in alcuni modi definiti dall’implementazione (in modo tale che solo q può essere dereferenziato). Questo potrebbe significare ciò dato quanto segue:

 // assume same 'struct foo' and 'f' declarations char *p = (char *)&f.bar; char *q = (char *)&f + offsetof(struct foo, bar); 

p e q confrontano lo stesso, ma potrebbero avere diversi limiti associati a loro, poiché i cast di (char *) provengono da puntatori a tipi incompatibili.


Per riassumere tutto, lo standard C non è del tutto chiaro su questo tipo di comportamento, e il tentativo di applicare altre parti dello standard (o, per lo meno, le mie interpretazioni) porta a conflitti. Quindi, è ansible definire container_of in modo rigorosamente conforms? In tal caso, la definizione sopra è corretta?


Questo è stato discusso qui dopo i commenti sulla mia risposta a questa domanda.

Penso che sia rigorosamente conforms o che ci sia un grosso difetto nello standard. Facendo riferimento al tuo ultimo esempio, la sezione aritmetica del puntatore non fornisce al compilatore alcun margine di manovra per trattare p e q diverso. Non è condizionale su come è stato ottenuto il valore del puntatore, solo su quale object punta.

Qualsiasi interpretazione che p e q potrebbero essere trattati in modo diverso nell’aritmetica del puntatore richiederebbe un’interpretazione che p e q non puntino allo stesso object. Dato che non esiste un comportamento dipendente dall’implementazione nel modo in cui hai ottenuto p e q ciò significherebbe che non puntano allo stesso object su alcuna implementazione. Ciò richiederebbe a sua volta che p == q sia falso su tutte le implementazioni e quindi renderebbe tutte le implementazioni effettive non conformi.

Voglio solo rispondere a questo bit.

 int arr[5][5] = ... int *p = &arr[0][0] + 5; int *q = &arr[1][0]; 

Questo non è UB. È certo che p è un puntatore a un elemento dell’array, purché solo all’interno dei limiti. In ogni caso punta al sesto elemento di un array di 25 elementi e può essere tranquillamente dereferenziato. Può anche essere incrementato o decrementato per accedere ad altri elementi dell’array.

Vedi n3797 S8.3.4 per C ++. La formulazione è diversa per C, ma il significato è lo stesso. In effetti gli array hanno un layout standard e sono ben educati rispetto ai puntatori.


Supponiamo per un momento che non sia così. Quali sono le implicazioni? Sappiamo che il layout di un array int [5] [5] è identico a int [25], non possono esserci padding, allineamento o altre informazioni estranee. Sappiamo anche che una volta che p e q sono stati formati e dato un valore, devono essere identici sotto ogni aspetto.

L’unica possibilità è che, se lo standard dice che è UB e lo scrittore del compilatore implementa lo standard, allora un compilatore sufficientemente vigile potrebbe (a) emettere una diagnostica basata sull’analisi dei valori dei dati o (b) applicare un’ottimizzazione che era dipendente su non allontanarsi dai confini dei sotto-array.

Con un po ‘di riluttanza devo ammettere che (b) è almeno una possibilità. Sono portato all’osservazione piuttosto strana che se si riesce a hide al compilatore le tue vere intenzioni, questo codice è garantito per produrre un comportamento definito, ma se lo fai allo scoperto potrebbe non farlo.