Sarebbe una buona idea usare le funzioni variadiche in una API C come mezzo per preservare l'ABI?

3

Supponiamo che tu stia progettando un'API C e una delle tue maggiori preoccupazioni è la stabilità ABI (verrà distribuita come libreria condivisa, o qualsiasi altra cosa).

Hai una funzione esportata

int foo_bar(int a, int b, int* c);

E senti che un giorno potresti aver bisogno di aggiungere uno o più parametri a questa funzione, ma non sai ancora quali.

Vieni con la seguente soluzione

int foo_bar(int a, int b, int* c, int ver,...) ;
#define FOO_BAR_V1 0xA9520F00

E documenta che ver è riservato e deve sempre essere impostato su FOO_BAR_V1 e i parametri aggiuntivi sono stati ignorati.

Quindi pianifichi che, quando fissi il parametro aggiuntivo necessario, ad esempio un buffer di output, esegui il seguente

int foo_bar(int a, int b, int* c, int ver,/* char* buff, int buffSize, int* outSize*/...) ;
#define FOO_BAR_V2 (FOO_BAR_V1 + 1)

Dopo aver scritto tutto questo, sembra che non ci sia molto da fare in questo caso solo avendo due funzioni esportate (c'è?) oltre a non dover trovare un nuovo nome, quindi suppongo che chiederò anche se qualcuno sa di qualche libreria che fa questo o qualcosa di simile?

    
posta Bwmat 06.08.2016 - 06:18
fonte

1 risposta

4

Forse il modo più sicuro e flessibile per preservare la compatibilità con le versioni precedenti in un'API consiste nel mettere i parametri in una struct e passare sia la struttura normale per valore, sia un puntatore alla struct (per riferimento). Quest'ultima (passare un puntatore) è necessaria per l'uso in una libreria condivisa per la retrocompatibilità a livello ABI (come è la necessità di preservare l'ordinamento e i tipi dei membri della struttura esistenti), ma la prima potrebbe essere leggermente più pulita e sicura per un'API che richiede la ricompilazione per ogni modifica.

Nelle future evoluzioni dell'API (e forse ABI) è quindi possibile aggiungere nuovi parametri, ma facoltativi.

Tuttavia, se i nuovi parametri non sono in qualche modo opzionali, penso che l'unica soluzione sicura e sana sia definire una nuova funzione (cioè con un nuovo nome) che fornisca la nuova interfaccia.

In alcuni casi può essere possibile utilizzare il pre-processore, magari insieme a riferimenti deboli (a volte chiamati "weak linking") nel linker, per rinominare le chiamate per usare la nuova funzione e il suo nuovo A P I, e allo stesso tempo preservare la compatibilità con le versioni precedenti B (ad es. quando il tipo di parametro cambia). NetBSD ha usato questo per evolvere le interfacce standard di libreria e chiamata di sistema in modo che i vecchi binari possano continuare a funzionare con nuovi kernel e nuove librerie, ma entro i loro limiti originali, mentre i nuovi binari possono sfruttare tutte le funzionalità di una nuova API. Per esempio. il nuovo tipo sottostante per time_t è più ampio di una volta, quindi la nuova libc deve offrire sia il vecchio 32% bittime() sia un nuovo 64 bit, eppure NetBSD pubblica solo un'API per time_t time(time_t *) . Vedi il README libc di NetBSD per una discussione approfondita (sotto heading Vecchie versioni delle routine di libreria ).

    
risposta data 13.06.2017 - 01:44
fonte

Leggi altre domande sui tag