Definizione di portatile C

6

Ho avuto una discussione enorme e animata con qualcuno su un gruppo allentato - il dibattito è questo:

Il mio argomento

  • Il codice portatile è quello che compila con vari compilatori e gira esattamente lo stesso su varie architetture senza modifiche.
  • Se il tuo codice dipende dalla sizeof (T) di un tipo integrale, per mantenerlo portatile devi usare i tipi in stdint.h come uint8_t o uint32_t
  • La maggior parte dei programmi C non banali implicano un livello di manipolazione bit a bit, quindi tutti dovrebbero sapere stdint.h

Il suo argomento

  • Lo standard C dice "Usa i tipi standard per la massima portabilità"
  • uint32_t ecc. non è garantito che sia presente su tutte le architetture, quindi non usarne nessuna
  • stdint.h è C99 quindi dovresti evitarlo, poiché i sistemi embedded possono supportare solo C89

Il mio contatore è che a meno che tu non conosca le dimensioni dei tuoi tipi, il tuo codice può essere fondamentalmente rotto, e anche se non usi stdint.h c'è sempre un file di intestazione che ha typedefs tipi di dimensioni esplicite (ad esempio Windows.h dichiara UINT8 , UINT32 , DWORD e così via)

L'unica risposta che ottengo è che se usi uint32_t e così via, non è portabile secondo lo standard C. Se il comportamento in fase di esecuzione cambia a causa delle dimensioni dei caratteri, ciò non significa che il codice non sia portatile.

Normalmente lo lascio andare, ma questo è in un forum in cui molti programmatori in erba C e C ++ vengono in aiuto, molti dei quali li conosco personalmente. Anche questo tizio sostiene di essere un allenatore professionista di programmatori C, quindi mi preoccupa ancora di più.

In quale altro modo posso convincere questo tizio che se usi i tipi di base come int o short , il tuo codice non essere portatile?

Si riduce alla definizione di "portatile" - si limita a compilare su vari compilatori, o deve anche funzionare allo stesso modo su ogni piattaforma su cui viene compilato.

Avrei pensato che la portabilità del tempo di esecuzione è ciò che conta - quali sono i tuoi commenti?

    
posta rep_movsd 29.07.2016 - 01:21
fonte

5 risposte

5

"Indietro quando", dovevo scrivere codice altamente portatile che richiedeva dimensioni di testo esatte per un database di dispositivi di piccole dimensioni che girava su qualsiasi piattaforma da 8 bit a 64 bit. Avevamo uno standard di codifica che imponeva dimensioni esatte per tutte le variabili; questo approccio ha reso più facile passare MISRA (tra le altre bontà).

Per farlo funzionare, ho scritto e mantenuto il mio "stdint.h" per le build c89, con l'appropriato # ifdef per usare quello standard o "mio".

Per quanto riguarda l'utilizzo del proprio stdint.h è "portatile", a patto che tu abbia ifdef appropriato, è più portatile che possa essere. Senza stdint.h, non ci sono tipi di dimensioni indipendenti dalla piattaforma in C, quindi devi "fare qualcosa". Hai ragione che int, short, ecc NON hanno dimensioni standard in C, e il loro utilizzo non è portabile se ti stai basando su dimensioni specifiche.

    
risposta data 29.07.2016 - 02:04
fonte
5

Penso che entrambi stiate cercando di dimostrare un punto controverso; "portatile" è un termine improprio comune, spesso le volte non è nemmeno necessario lasciare una piattaforma specifica per avere un'interruzione della "portabilità" (guarda qualsiasi aggiornamento importante per qualsiasi sistema operativo principale).

I tuoi argomenti sono basati esclusivamente su bit. Se questo è il caso, la "portabilità" può rompersi quando si passa tra architetture con larghezza di bit diversa (vedi CHAR_BIT ).

Inoltre, molti standard (e molte altre lingue) hanno una clausola per molte delle semantiche che indicano che qualcosa è implementation defined , il che significa automaticamente che non è "portatile" (come non puoi essere garantito sarà disponibile).

Penso che se vuoi diventare veramente tecnico, dovresti scomporre il significato della parola "portatile": portare qualcosa significa passare da una piattaforma all'altra.

Se il codice è abilitabile alla porta, posso eseguire il porting del codice da una piattaforma a un'altra senza incidenti (ad esempio, il codice verrà eseguito come previsto). Ma questo non significa che devo cambiare il compilatore . La modifica del compilatore presuppone automaticamente che non possa più garantire che il mio codice "porti" senza incidenti.

Se sto cercando di garantire che il mio codice funzioni come previsto tra i compilatori, allora non sto testando la portabilità tra i sistemi ma la conformità tra i compilatori con lo standard C specifico che sto codificando (es. C89 / C99 / ISO / ANSI / Embedded C, ecc.).

C non è al 100% portatile in particolare a causa di ciò che è destinato a fare; essere il più vicino possibile all'hardware.

    
risposta data 29.07.2016 - 02:20
fonte
3

La portabilità è sempre all'interno di alcune piattaforme. C non è un linguaggio perfettamente portatile; è possibile costruire macchine su cui non è possibile implementare C (o su cui non è possibile implementare C ragionevolmente bene). Quindi la questione della portabilità è sempre una delle varie piattaforme supportate.

Usando C, supportate alcuni sottoinsiemi di piattaforme che hanno compilatori C funzionanti. Ovviamente. Ma quello che fai con C può restringere il supporto.

If your code depends on the sizeof(T) of an integral type, in order to keep it portable you should use the types in stdint.h like uint8_t or uint32_t

Se scrivi un codice che dipende dalla dimensione di un tipo, non stai scrivendo un codice che sia portatile come C stesso. Come sei stato informato, uint8_t e tali tipi sono non richiesti dallo standard C. Con qualsiasi standard C; sono funzioni facoltative di C99 e C11. E C ++ 11 e versioni successive, nel caso ve lo stiate chiedendo.

Ma portatile "per quanto riguarda lo standard" non è necessariamente la fine della portabilità. Dopo tutto ci sono molte piattaforme che possono e supportano questi tipi. Il tuo codice può essere trasferito su tali piattaforme.

Ciò di cui ti stai veramente preoccupando non è tanto la dipendenza dai tipi di dimensioni. Ciò di cui sei stato avvisato è affidarsi alle dimensioni del tipo . È quello che limita le tue piattaforme, perché C non definisce le dimensioni del testo. C consente alle singole piattaforme di farlo.

Pertanto, il codice che si basa sull'avere un tipo intero veloce a 32 bit è per sua stessa natura non portabile su piattaforme che non dispongono di tali elementi. A titolo di esempio, Vulkan si autoproclama come API grafica multipiattaforma. E questo è. Ma Vulkan definisce tutti i suoi tipi in termini di tipi interi di stdint.h . Perché?

Perché se una piattaforma non può supportarli loro , la piattaforma non può supportare il periodo Vulkan. Quindi non ha senso fingere che Vulkan possa funzionare su qualsiasi cosa C possa.

Questo non è sbagliato; sta semplicemente impostando i tuoi limiti in base alle esigenze del tuo codice. Spesso invii strutture dati direttamente da e verso Vulkan, e devi essere in grado di far corrispondere ciò che le implementazioni si aspettano / richiedono. Quindi la tua piattaforma C deve supportare tipi che possano combaciare con questi.

Allo stesso tempo, se non stai facendo qualcosa che richiede esplicitamente una dimensione specifica di numero intero, allora stai facendo a te stesso un disservizio usando un tipo intero di dimensioni esplicite quando un int farebbe. I tipi int_leastXX_t sono obbligatori e possono gestire la maggior parte dei problemi relativi agli intervalli interi.

In what other way can I convince this guy that if you use the basic types like int or short, your code will not be portable?

L'uso di questi tipi sarà non portabile solo se fai cose non portabili con loro.

Ad esempio, la manipolazione dei bit è ben definita in C. Il numero specifico di bit in int non lo è. Quindi il seguente codice:

unsigned int i = 1 << 19;

È perfetto, ben definito ... a patto che sizeof(unsigned int) sia 20 bit o più su quella piattaforma. Quindi qualsiasi piattaforma in cui questo è vero andrà bene.

Al contrario:

uint32_t i = 1 << 19;

È solo un codice ben definito se la piattaforma supporta uint32_t . Se ha una dimensione di byte insolita (9 e 18 bit non sono dimensioni di byte sconosciute), quindi non può supportare uint32_t . E quindi quel codice non può funzionare. Ma potrebbe aver funzionato per il primo caso, perché int poteva avere una dimensione di 36 bit.

Limitando te stesso ai tipi di numeri interi specifici, ti limiti anche alle piattaforme che supportano questi tipi di numeri interi. Se usi i tipi interi fondamentali, sei limitato alle piattaforme in cui tali tipi sono "abbastanza grandi" per qualsiasi cosa li usi.

Quindi qual è il più portatile? È difficile sostenere che quello più grande sia il più portatile, quando quello non dimensionato può essere eseguito su qualsiasi cosa (che supporti C) che ha numeri interi sufficientemente grandi. Mentre la dimensione è limitata solo alle piattaforme che supportano numeri interi a 32 bit. Anche se quel codice non sta effettivamente usando 32 bit.

Suppongo che un vantaggio di quest'ultimo sia che, se i tipi di dimensioni non sono supportati, si ottiene un errore di compilazione difficile. Al contrario, se int non è abbastanza grande, si ottiene un comportamento invisibile definito dall'implementazione. C ++ 11 ha ottenuto una soluzione a questo problema con static_assert , che potrebbe impedire la compilazione su una cosa arbitraria in fase di compilazione. Come la dimensione di un int che è abbastanza grande per un uso particolare.

    
risposta data 29.07.2016 - 02:16
fonte
3

La piattaforma li avrà?

I tipi interi a larghezza fissa sono presenti nei rispettivi standard di ogni lingua da C + 11 e C99. Non sono sicuro che il primo argomento del tuo avversario abbia senso.

Sono comunque opzionali. Non esistono se non esiste un tipo intero nativo di tale larghezza. Questa è un'obiezione completamente valida.

Supponiamo che tu stia scrivendo su una piattaforma in cui il sapore nativo int è di 32 bit e successivamente scegli come target una piattaforma a 64 bit senza tipo nativo a 32 bit. Se utilizzi int32_t il tuo codice non verrà compilato. Se si usa int lo farà, e finché non si sono fatte ipotesi a 32 bit, funzionerà correttamente. Vinci per int , è più portatile . D'altra parte, la mancata compilazione riguarda l'aspetto più trasparente, più rapido e più facile da risolvere in modo che un programma possa fallire. Se hai fatto un'ipotesi a 32 bit e hai usato int , hai un bug di runtime specifico della piattaforma orribile. Divertimento. Vinci per int_32t .

Quindi è un trade-off. Usare numeri interi a larghezza fissa significa che il tuo codice è conforme a meno piattaforme, ma puoi avere più fiducia che funzionerà come previsto una volta fatto. L'utilizzo di numeri interi non fissi significa che verrà compilato su più piattaforme, ma sarà necessario eseguire molti più controlli di qualità su queste piattaforme diverse. Il che è più portabile dipende dalla tua idea di cosa significhi la portabilità. Sono d'accordo con te, perché sono avverso al rischio e voglio una vita tranquilla.

C89?

Giusto, il tuo avversario è in qualche modo giusto in senso accademico. Tecnicamente perché non farlo compilare su K & C. Anche dopo che C89 è uscito, le persone scrivevano con la compatibilità di K & R in mente. Ci siamo fermati perché non volevamo rinunciare a molte funzioni interessanti che rendono il codice migliore. Imho, a meno che tu non abbia un noto bisogno di indirizzare i vecchi compilatori, scrivere per C89 è folle (Scrivere per pre C + 11 è ancora più folle). Questo è il problema con le conversazioni accademiche non fondate.

La maggior parte dei programmi C non banali coinvolge alcuni ...

Forse. Tendo ad assumere che ho fatto la cosa più stupida possibile da qualche parte nel codice, perché non mi piacciono i bug. Quindi userò la larghezza fissa per sicurezza. Ma i programmi C variano molto. Non puoi nemmeno dire che tutti i programmi non banali usano i puntatori. Quindi questo è il tipo di dichiarazione che puoi aspettarti di essere abbattuto.

    
risposta data 29.07.2016 - 02:05
fonte
0

Non appena il tuo codice deve rendere qualsiasi ipotesi su dimensioni o rappresentazioni del tipo oltre a quelle che la definizione della lingua garantisce , limiti la sua portabilità. Se il tuo codice presuppone che un oggetto int debba essere esattamente di 32 bit di larghezza, allora non sarà banalmente trasportabile 1 in sistemi con 16 bit int s o sistemi in cui CHAR_BIT non è 8, ecc.

OTOH, se si dà per scontato che un int debba essere in grado di memorizzare l'intervallo di valori [-32767..32767] , allora il codice è portabile a qualsiasi (conforme) sistema senza modifiche.

I tipi di dimensione fissa semplificano la vita su piattaforme in cui sono supportati, ma non renderanno il tuo codice più in generale più portabile.

Modifica

Per rep_movsd, capitolo e verso :

5.2.4.2.1 Sizes of integer types <limits.h>

1     The values given below shall be replaced by constant expressions suitable for use in #if preprocessing directives. Moreover, except for CHAR_BIT and MB_LEN_MAX, the following shall be replaced by expressions that have the same type as would an expression that is an object of the corresponding type converted according to the integer promotions. Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.
...
— minimum value for an object of type int
   INT_MIN -32767 // −(215 − 1)

— maximum value for an object of type int
   INT_MAX +32767 // 215 − 1

...
6.2.5 Types
...
5     An object declared as type signed char occupies the same amount of storage as a ‘‘plain’’ char object. A ‘‘plain’’ int object has the natural size suggested by the architecture of the execution environment (large enough to contain any value in the range INT_MIN to INT_MAX as defined in the header <limits.h>).

Enfasi aggiunta.

Sulla maggior parte delle architetture moderne int ha una larghezza di almeno 32 bit, ma non è universale. Sono stato morso da quella parte negli anni '90; stavamo lavorando su codice che doveva girare su MacOS 8 e Win 3.1. Ho scritto un codice che presupponeva unint di 32 bit che funzionava bene sul Mac ma che si è verificato sulla macchina Windows, che utilizzava ancora il 16% di int .

  1. Significa che puoi semplicemente ricompilare il nuovo target senza cambiare la fonte.

risposta data 09.08.2016 - 17:42
fonte

Leggi altre domande sui tag