Domanda

Ho lavorato su una serie di diversi sistemi embedded.L'hanno usato tutti typedefs (o #defines) per tipi come UINT32.

Questa è una buona tecnica poiché fa capire al programmatore la dimensione del tipo e ti rende più consapevole delle possibilità di overflow, ecc.

Ma su alcuni sistemi sai che il compilatore e il processore non cambieranno per tutta la durata del progetto.

Quindi cosa dovrebbe influenzare la tua decisione di creare e applicare tipi specifici del progetto?

Modifica penso di essere riuscito a perdere l'essenza della mia domanda, e forse sono davvero due.

Con la programmazione integrata potrebbero essere necessari tipi di dimensioni specifiche per le interfacce e anche per far fronte a risorse limitate come la RAM.Questo non può essere evitato, ma puoi scegliere di utilizzare i tipi di base del compilatore.

Per tutto il resto le tipologie hanno meno importanza.
È necessario fare attenzione a non causare overflow e potrebbe essere necessario fare attenzione all'utilizzo del registro e dello stack.Il che potrebbe portarti a UINT16, UCHAR.Utilizzando tipi come UCHAR è tuttavia possibile aggiungere "fluff" al compilatore.Poiché i registri sono in genere più grandi, alcuni compilatori potrebbero aggiungere codice per forzare il risultato nel tipo.

i++;
può diventare
ADD REG,1
AND REG, 0xFF
che non è necessario.

Quindi penso che la mia domanda avrebbe dovuto essere: -

dati i vincoli del software incorporato, qual è la politica migliore da impostare per un progetto su cui lavoreranno molte persone, non tutte dello stesso livello di esperienza.

È stato utile?

Soluzione

Uso l'astrazione del tipo molto raramente.Ecco le mie argomentazioni, ordinate in ordine crescente di soggettività:

  1. Le variabili locali sono diverse dai membri della struttura e dagli array nel senso che desideri che si adattino a un registro.Su un target 32b/64b, un file local int16_t può rendere il codice più lento rispetto a un int locale poiché il compilatore dovrà aggiungere operazioni a /force/ overflow in base alla semantica di int16_t.Mentre C99 definisce un intfast_t typedef, per quanto ne so, un semplice int si adatterà altrettanto bene a un registro, ed è sicuramente un nome più breve.

  2. Le organizzazioni a cui piacciono queste typedef finiscono quasi invariabilmente per averne molte (INT32, int32_t, INT32_T, verso l'infinito).Le organizzazioni che utilizzano tipi incorporati si trovano quindi meglio, in un certo senso, ad avere un solo set di nomi.Vorrei che le persone usassero i typedef di stdint.h o windows.h o qualsiasi cosa esistente;e quando una destinazione non ha quel file .h, quanto è difficile aggiungerne uno?

  3. Le typedef possono teoricamente aiutare la portabilità, ma io, per esempio, non ho mai guadagnato nulla da loro.Esiste un sistema utile che puoi trasferire da un target 32b a uno 16b?Esiste un sistema 16b che non sia banale da portare su un target 32b?Inoltre, se la maggior parte delle variabili sono int, otterrai effettivamente qualcosa dai 32 bit sul nuovo target, ma se lo sono int16_t, non lo farai.E i luoghi difficili da portare tendono comunque a richiedere un’ispezione manuale;prima di provare un porto, non sai dove sono.Ora, se qualcuno pensa che sia così facile portare le cose se hai typedef ovunque - quando arriva il momento del port, cosa che accade a pochi sistemi, scrivi uno script che converte tutti i nomi nella base di codice.Ciò dovrebbe funzionare secondo la logica "non è richiesta alcuna ispezione manuale" e posticipa lo sforzo al momento in cui dà effettivamente beneficio.

  4. Ora, se la portabilità può essere un vantaggio teorico delle typedef, leggibilità sicuramente va in malora.Basta guardare stdint.h: {int,uint}{max,fast,least}{8,16,32,64}_t.Molti tipi.Un programma ha molte variabili;è davvero così facile capire quale deve essere int_fast16_t e quali devono essere uint_least32_t?Quante volte ci convertiamo silenziosamente tra loro, rendendoli del tutto inutili?(Mi piacciono particolarmente le conversioni BOOL/Bool/eBool/boolean/bool/int.Ogni programma scritto da un'organizzazione ordinata che impone typedef ne è pieno).

  5. Ovviamente in C++ potremmo rendere il sistema di tipi più rigido, racchiudendo i numeri nelle istanziazioni delle classi template con operatori e cose sovraccariche.Ciò significa che ora riceverai messaggi di errore nel formato "class Number<int,Least,32> non ha un sovraccarico di operator+ per argomenti di tipo class Number<unsigned long long,Fast,64>, i candidati sono..." I non chiamate nemmeno questa "leggibilità".Le tue possibilità di implementare correttamente queste classi wrapper sono microscopiche e la maggior parte delle volte aspetterai la compilazione delle innumerevoli istanziazioni del modello.

Altri suggerimenti

Lo standard C99 prevede una serie di tipi interi di dimensioni standard.Se puoi usare un compilatore che supporta C99 (gcc lo fa), li troverai in <stdint.h> e puoi semplicemente usarli nei tuoi progetti.

Inoltre, può essere particolarmente importante nei progetti incorporati utilizzare i tipi come una sorta di "rete di sicurezza" per cose come le conversioni di unità.Se puoi usare C++, capisco che ci sono alcune librerie "di unità" che ti consentono di lavorare in unità fisiche definite dal sistema di tipi C++ (tramite modelli) che vengono compilate come operazioni sui tipi scalari sottostanti.Ad esempio, queste librerie non ti consentiranno di aggiungere un file distance_t ad a mass_t perché le unità non si allineano;in realtà otterrai un errore del compilatore.

Anche se non puoi lavorare in C++ o in un altro linguaggio che ti consenta di scrivere codice in questo modo, puoi almeno utilizzare il sistema di tipi C per aiutarti a individuare errori del genere a occhio.(Questo era in realtà l'intento originale della notazione ungherese di Simonyi.) Solo perché il compilatore non ti sgriderà per aver aggiunto un meter_t ad a gram_t non significa che non dovresti usare tipi del genere.Le revisioni del codice saranno quindi molto più produttive nello scoprire errori unitari.

La mia opinione è se dipendi da una dimensione minima/massima/specifica non supponi semplicemente che (diciamo) an unsigned int è 32 byte: utilizzare uint32_t invece (supponendo che il tuo compilatore supporti C99).

Mi piace usare i tipi stdint.h per definire le API di sistema in modo specifico perché dicono esplicitamente quanto sono grandi gli elementi.Ai vecchi tempi di Palm OS, le API di sistema venivano definite utilizzando una serie di tipi insipidi come "Word" e "SWord" ereditati dal classico sistema operativo Mac.Hanno fatto una pulizia per dire invece Int16 e hanno reso l'API più facile da comprendere per i nuovi arrivati, specialmente con gli strani problemi del puntatore a 16 bit su quel sistema.Durante la progettazione di Palm OS Cobalt, hanno cambiato nuovamente quei nomi per farli corrispondere ai nomi di stdint.h, rendendolo ancora più chiaro e riducendo la quantità di typedef che dovevano gestire.

Credo che gli standard MISRA suggeriscano (richiedano?) l'uso di typedef.

Da un punto di vista personale, l'utilizzo di typedef non lascia confusione riguardo alla dimensione (in bit/byte) di determinati tipi.Ho visto gli sviluppatori principali tentare entrambi i modi di sviluppo utilizzando tipi standard, ad es.int e utilizzando tipi personalizzati, ad es.UINT32.

Se il codice non è portabile c'è poco vero vantaggio nell'uso delle typedef, Tuttavia , se come me poi lavori su entrambe le tipologie di software (ambiente portatile e fisso) allora può essere utile mantenere uno standard e utilizzare tipologie personalizzate.Per lo meno, come dici tu, il programmatore è quindi molto consapevole di quanta memoria sta utilizzando.Un altro fattore da considerare è quanto sei sicuro che il codice non verrà trasferito in un altro ambiente?Ho visto che il codice specifico del processore deve essere tradotto poiché un ingegnere hardware ha dovuto improvvisamente cambiare una scheda, questa non è una bella situazione in cui trovarsi ma a causa delle typedef personalizzate avrebbe potuto andare molto peggio!

Coerenza, comodità e leggibilità."UINT32" è molto più leggibile e scrivibile di "unsigned long long", che è l'equivalente per alcuni sistemi.

Inoltre, il compilatore e il processore potrebbero essere riparati per la durata di un progetto, ma il codice di quel progetto potrebbe trovare nuova vita in un altro progetto.In questo caso, avere tipi di dati coerenti è molto conveniente.

Se i tuoi sistemi embedded sono in qualche modo a sistema critico per la sicurezza (o simile), è fortemente consigliato (se non richiesto) per utilizzare typedef su tipi semplici.

COME TK. ha detto prima, MISRA-C ha una regola (consultiva) per farlo:

Regola 6.3 (avvistiva): typedef che indicano dimensione e segno dovrebbero essere usati al posto dei tipi numerici di base.

(da MISRA-C 2004;è la regola n. 13 (avv) del MISRA-C 1998)


Lo stesso vale anche per il C++ in quest'area;per esempio. Standard di codifica JSF C++:

Regola AV 209 Verrà creato un file universaltypes per definire tutti i tipi di stard per gli sviluppatori da utilizzare.I tipi includono:[uint16, int16, uint32_t ecc.]

Utilizzando <stdint.h> rende il tuo codice più portabile per i test unitari su un PC.

Può morderti piuttosto forte quando hai test per tutto, ma si rompe comunque sul tuo sistema di destinazione perché un int improvvisamente è lungo solo 16 bit.

Forse sono strano, ma uso ub, ui, ul, sb, si e sl per i miei tipi interi.Forse la "i" per 16 bit sembra un po' datata, ma mi piace l'aspetto di ui/si meglio di uw/sw.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top