Ha senso un divieto "lungo"?


109

Nel cross-platform di oggi ++ (o C) C mondo che abbiamo :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

Ciò che significa oggi, è che per qualsiasi numero intero "comune" (firmato), intsarà sufficiente e può ancora essere usato come tipo intero predefinito quando si scrive il codice dell'applicazione C ++. Inoltre, per gli attuali scopi pratici, avrà una dimensione coerente su tutte le piattaforme.

Se un caso d'uso richiede almeno 64 bit, oggi possiamo usarlo long long, sebbene possibilmente usare uno dei tipi che specificano il testimone o il __int64tipo potrebbe avere più senso.

Questo lascia longa metà e stiamo prendendo in considerazione l'idea di vietare l'utilizzo del longnostro codice dell'applicazione .

Questo avrebbe senso , o esiste un caso per l'utilizzo longnel moderno codice C ++ (o C) che deve essere eseguito su più piattaforme? (piattaforma essendo desktop, dispositivi mobili, ma non cose come microcontrollori, DSP ecc.)


Link di sfondo forse interessanti:


14
Come gestirai le chiamate alle librerie che usano a lungo?
Ángel

14
longè l'unico modo per garantire 32 bit. intpuò essere 16 bit, quindi per alcune applicazioni non è sufficiente. Sì, a intvolte è 16 bit sui compilatori moderni. Sì, le persone scrivono software su microcontrollori. Direi che più persone scrivono software che ha più utenti su microcontrollori che su PC con l'ascesa di dispositivi iPhone e Android per non parlare dell'ascesa di Arduinos ecc.
Slebetman,

53
Perché non vietare char, short, int, long e long long e usare i tipi [u] intXX_t?
immibis,

7
@slebetman Ho scavato un po 'più a fondo, sembra che il requisito sia ancora in atto, sebbene nascosto nel §3.9.1.3 dove lo standard C ++ afferma: "I tipi interi con segno e senza segno devono soddisfare i vincoli indicati nello standard C, sezione 5.2. 4.2.1." E nello standard C §5.2.4.2.1 indica l'intervallo minimo, esattamente come hai scritto. Avevi assolutamente ragione. :) Apparentemente possedere una copia dello standard C ++ non è sufficiente, è necessario trovare anche la copia dello standard C.
Tommy Andersen,

11
Ti manca il mondo DOSBox / Turbo C ++ in cui intsono ancora presenti 16 bit. Odio dirlo, ma se hai intenzione di scrivere del "mondo multipiattaforma di oggi", non puoi ignorare l'intero subcontinente indiano.
Razze di leggerezza in orbita

Risposte:


17

L'unico motivo che vorrei usare longoggi è quando si chiama o si implementa un'interfaccia esterna che lo utilizza.

Come dici nel tuo post, short e int hanno caratteristiche ragionevolmente stabili su tutte le principali piattaforme desktop / server / mobili di oggi e non vedo alcun motivo per cambiare nel prossimo futuro. Quindi vedo poche ragioni per evitarle in generale.

longd'altra parte è un casino. Su tutti i sistemi a 32 bit sono consapevole che aveva le seguenti caratteristiche.

  1. Aveva esattamente 32 bit di dimensione.
  2. Aveva le stesse dimensioni di un indirizzo di memoria.
  3. Aveva le stesse dimensioni della più grande unità di dati che poteva essere conservata in un registro normale e lavorare con un'unica istruzione.

Grandi quantità di codice sono state scritte sulla base di una o più di queste caratteristiche. Tuttavia con il passaggio a 64 bit non è stato possibile preservarli tutti. Le piattaforme simili a Unix hanno optato per LP64 che ha conservato le caratteristiche 2 e 3 al costo della caratteristica 1. Win64 ha optato per LLP64 che ha conservato la caratteristica 1 al costo delle caratteristiche 2 e 3. Il risultato è che non puoi più fare affidamento su nessuna di quelle caratteristiche e che l'IMO lascia poche ragioni per l'uso long.

Se si desidera un tipo di dimensioni esattamente a 32 bit, è necessario utilizzare int32_t.

Se vuoi un tipo che abbia le stesse dimensioni di un puntatore, dovresti usare intptr_t(o meglio uintptr_t).

Se si desidera un tipo che è l'elemento più grande su cui è possibile lavorare in un singolo registro / istruzione, purtroppo non credo che lo standard ne preveda uno. size_tdovrebbe essere proprio sulle piattaforme più comuni ma non su x32 .


PS

Non mi preoccuperei dei tipi "veloce" o "minimo". I tipi "minimi" contano solo se ti preoccupi della portabilità per oscurare davvero architetture dove CHAR_BIT != 8. La dimensione dei tipi "veloci" in pratica sembra essere piuttosto arbitraria. Linux sembra renderli almeno della stessa dimensione del puntatore, che è stupido su piattaforme a 64 bit con supporto rapido a 32 bit come x86-64 e arm64. IIRC iOS li rende il più piccolo possibile. Non sono sicuro di cosa facciano altri sistemi.


PPS

Un motivo da usare unsigned long(ma non semplice long) è perché è garantito per avere un comportamento modulo. Sfortunatamente a causa delle regole di promozione sbagliate di C tipi non firmati più piccoli di quelli intche non hanno un comportamento modulo.

Oggi su tutte le piattaforme principali ha uint32_tle stesse dimensioni o è più grande di int e quindi ha un comportamento modulo. Tuttavia ci sono stati storicamente e teoricamente potrebbe esserci nelle piattaforme future dove intè a 64 bit e quindi uint32_tnon ha un comportamento modulo.

Personalmente direi che è meglio entrare nell'abitudine di forzare il comportamento del modulo usando "1u *" o "0u +" all'inizio delle equazioni in quanto funzionerà per qualsiasi dimensione di tipo senza segno.


1
Tutti i tipi di "dimensioni specificate" sarebbero molto più utili se potessero specificare la semantica che differiva dai tipi predefiniti. Ad esempio, sarebbe utile disporre di un tipo che utilizzerebbe mod-65536 aritmetica indipendentemente dalle dimensioni di "int", insieme a un tipo che sarebbe in grado di contenere i numeri da 0 a 65535 ma potrebbe arbitrariamente e non necessariamente coerente poter di contenere numeri più grandi di quello. Quale tipo di dimensione sia più veloce dipenderà dalla maggior parte delle macchine dal contesto, quindi essere in grado di lasciare arbitrariamente il compilatore sarebbe ottimale per la velocità.
supercat,

204

Come accennato nella tua domanda, il software moderno riguarda l'interoperabilità tra piattaforme e sistemi su Internet. Gli standard C e C ++ forniscono intervalli per dimensioni di tipo intero, non dimensioni specifiche (in contrasto con linguaggi come Java e C #).

Per garantire che il tuo software compilato su piattaforme diverse funzioni allo stesso modo con gli stessi dati e per garantire che altri software possano interagire con il tuo software utilizzando le stesse dimensioni, devi utilizzare numeri interi di dimensioni fisse.

Immettere <cstdint>quale fornisce esattamente questo ed è un'intestazione standard che tutte le piattaforme di compilatore e di libreria standard devono fornire. Nota: questa intestazione era richiesta solo a partire da C ++ 11, ma molte implementazioni di librerie precedenti la fornivano comunque.

Desideri un numero intero senza segno a 64 bit? Usa uint64_t. Numero intero a 32 bit firmato? Usa int32_t. Mentre i tipi nell'intestazione sono opzionali, le piattaforme moderne dovrebbero supportare tutti i tipi definiti nell'intestazione.

A volte è necessaria una larghezza di bit specifica, ad esempio, in una struttura di dati utilizzata per comunicare con altri sistemi. Altre volte non lo è. Per situazioni meno rigide, <cstdint>fornisce tipi di larghezza minima.

Ci sono meno varianti: int_leastXX_tsarà un tipo intero di almeno XX bit. Utilizzerà il tipo più piccolo che fornisce XX bit, ma il tipo può essere maggiore del numero specificato di bit. In pratica, questi sono in genere gli stessi dei tipi sopra descritti che danno il numero esatto di bit.

Ci sono anche varianti veloci : int_fastXX_tè almeno XX bit, ma dovrebbe usare un tipo che funziona velocemente su una particolare piattaforma. La definizione di "veloce" in questo contesto non è specificata. Tuttavia, in pratica, ciò significa in genere che un tipo più piccolo delle dimensioni del registro di una CPU può essere alias di un tipo delle dimensioni del registro della CPU. Ad esempio, l'intestazione di Visual C ++ 2015 specifica che int_fast16_tè un numero intero a 32 bit poiché l'aritmetica a 32 bit è complessivamente più veloce sull'aritmetica x86 rispetto a 16 bit.

Ciò è importante perché dovresti essere in grado di utilizzare tipi in grado di contenere i risultati dei calcoli eseguiti dal tuo programma indipendentemente dalla piattaforma. Se un programma produce risultati corretti su una piattaforma ma risultati errati su un'altra a causa delle differenze di overflow dei numeri interi, ciò è negativo. Utilizzando i tipi interi standard, si garantisce che i risultati su piattaforme diverse saranno gli stessi per quanto riguarda la dimensione degli interi utilizzati (ovviamente potrebbero esserci altre differenze tra le piattaforme oltre alla larghezza intera).

Quindi sì, longdovrebbe essere bandito dal moderno codice C ++. Così dovrebbe int, shorte long long.


20
Vorrei avere altri cinque account per aumentare ulteriormente il voto.
Steven Burnap,

4
+1, ho affrontato alcuni strani errori di memoria che si verificano solo quando le dimensioni di una struttura dipendono dal computer su cui stai compilando.
Joshua Snider,

9
@Wildcard è un'intestazione C che fa anche parte di C ++: vedere il prefisso "c" su di esso. C'è anche un modo per mettere i typedef nello stdspazio dei nomi quando #includein un'unità di compilazione C ++, ma la documentazione che ho collegato non lo menziona e Visual Studio sembra non preoccuparsi di come accedervi.

11
Il divieto intpuò essere ... eccessivo? (Lo prenderei in considerazione se il codice deve essere estremamente portabile su tutte le piattaforme oscure (e non così oscure). Proibirlo per "codice app" potrebbe non stare molto bene con i nostri sviluppatori.
Martin Ba

5
@Snowman #include <cstdint>è tenuto a inserire i tipi std::e (purtroppo) facoltativamente anche a metterli nello spazio dei nomi globale. #include <stdint.h>è esattamente il contrario. Lo stesso vale per qualsiasi altra coppia di intestazioni C. Vedi: stackoverflow.com/a/13643019/2757035 Vorrei che lo Standard avesse richiesto a ciascuno di influire solo sul rispettivo spazio dei nomi richiesto - piuttosto che apparentemente inarcarsi con le convenzioni scadenti stabilite da alcune implementazioni - ma vabbè, eccoci qui.
underscore_d

38

No, vietare i tipi interi incorporati sarebbe assurdo. Tuttavia, non dovrebbero essere maltrattati.

Se hai bisogno di un numero intero largo esattamente N bit, usa (o se hai bisogno di una versione). Pensare come numero intero a 32 bit e come numero intero a 64 bit è semplicemente sbagliato. Potrebbe accadere che sia così sulle tue piattaforme attuali ma questo si basa su comportamenti definiti dall'implementazione.std::intN_tstd::uintN_tunsignedintlong long

L'uso di tipi interi a larghezza fissa è utile anche per interagire con altre tecnologie. Ad esempio, se alcune parti dell'applicazione sono scritte in Java e altre in C ++, probabilmente vorrai abbinare i tipi interi in modo da ottenere risultati coerenti. (Siate ancora consapevoli che l'overflow in Java ha una semantica ben definita mentre l' signedoverflow in C ++ è un comportamento indefinito, quindi la coerenza è un obiettivo elevato.) Saranno anche preziosi per lo scambio di dati tra diversi host di elaborazione.

Se non hai bisogno esattamente di N bit, ma solo di un tipo sufficientemente largo , considera l'utilizzo di (ottimizzato per lo spazio) o (ottimizzato per la velocità). Anche in questo caso, entrambe le famiglie hanno anche controparti.std::int_leastN_tstd::int_fastN_tunsigned

Quindi, quando utilizzare i tipi predefiniti? Bene, poiché lo standard non specifica con precisione la loro larghezza, usali quando non ti interessa la larghezza effettiva della punta ma altre caratteristiche.

A charè il numero intero più piccolo indirizzabile dall'hardware. Il linguaggio in realtà ti costringe a usarlo per aliasing della memoria arbitraria. È anche l'unico tipo possibile per rappresentare stringhe di caratteri (strette).

Di intsolito è il tipo più veloce che la macchina è in grado di gestire. Sarà abbastanza largo da poter essere caricato e memorizzato con una singola istruzione (senza dover mascherare o spostare i bit) e abbastanza stretto da poter essere utilizzato con (le) istruzioni hardware più efficienti. Pertanto, intè una scelta perfetta per il passaggio di dati e l'esecuzione dell'aritmetica quando l'overflow non è un problema. Ad esempio, il tipo di enumerazione sottostante predefinito è int. Non cambiarlo in un numero intero a 32 bit solo perché puoi. Inoltre, se si dispone di un valore che può essere solo -1, 0 e 1, unintè una scelta perfetta, a meno che non ne memorizzi enormi matrici, nel qual caso potresti voler utilizzare un tipo di dati più compatto al costo di dover pagare un prezzo più elevato per accedere ai singoli elementi. Una cache più efficiente probabilmente ripagherà per questi. Molte funzioni del sistema operativo sono anche definite in termini di int. Sarebbe sciocco convertire i loro argomenti e risultati avanti e indietro. Tutto ciò che potrebbe eventualmente fare è introdurre errori di overflow.

longdi solito sarà il tipo più largo che può essere gestito con le istruzioni della singola macchina. Ciò rende particolarmente unsigned longinteressante la gestione di dati grezzi e tutti i tipi di manipolazione dei bit. Ad esempio, mi aspetto di vedere unsigned longnell'implementazione di un vettore bit. Se il codice viene scritto con cura, non importa quanto sia effettivamente largo il tipo (perché il codice si adatterà automaticamente). Su piattaforme in cui la parola-macchina nativa è a 32 bit, la matrice di supporto del vettore-bit può essere una matrice diunsignedI numeri interi a 32 bit sono più desiderabili perché sarebbe sciocco usare un tipo a 64 bit che deve essere caricato tramite istruzioni costose solo per spostare e mascherare nuovamente i bit non necessari. D'altra parte, se la dimensione della parola nativa della piattaforma è 64 bit, desidero un array di quel tipo perché significa che operazioni come "trova il primo set" possono essere fino a due volte più veloci. Quindi il "problema" del longtipo di dati che stai descrivendo, che le sue dimensioni variano da piattaforma a piattaforma, in realtà è una funzionalità che può essere sfruttata bene. Diventa un problema solo se pensi ai tipi predefiniti come tipi di una certa larghezza di bit, che semplicemente non lo sono.

char, intE longsono tipi molto utili come descritto sopra. shorte long longnon sono altrettanto utili perché la loro semantica è molto meno chiara.


4
L'OP ha sottolineato in particolare la differenza nelle dimensioni longtra Windows e Unix. Potrei essere frainteso, ma la tua descrizione della differenza nella dimensione di longessere una "caratteristica" anziché un "problema" ha senso per me per il confronto di modelli di dati a 32 e 64 bit, ma non per questo particolare confronto. Nel caso particolare di questa domanda, è davvero una caratteristica? O è una caratteristica in altre situazioni (cioè, in generale), e innocua in questo caso?
Dan Getz,

3
@ 5gon12eder: il problema è che tipi come uint32_t sono stati creati allo scopo di consentire al comportamento del codice di essere indipendente dalla dimensione di "int", ma la mancanza di un tipo il cui significato sarebbe "comportarsi come un uint32_t funziona su un 32- bit system "rende molto più difficile la scrittura di codice il cui comportamento è indipendente dalla dimensione di" int "rispetto alla scrittura di codice che è quasi corretto.
supercat,

3
Sì, lo so ... ecco da dove venivano le imprecazioni. Gli autori originali hanno appena intrapreso la strada della resistenza al leasing perché quando hanno scritto il codice, i sistemi operativi a 32 bit erano a distanza di oltre un decennio.
Steven Burnap,

8
@ 5gon12eder Purtroppo, il supercat ha ragione. Tutti i tipi di larghezza esatta sono "solo typedef" e le regole di promozione dei numeri interi non se ne accorgono, il che significa che l'aritmetica sui uint32_tvalori verrà eseguita come aritmetica firmata , a larghezza larga , intsu una piattaforma dove intè più ampia di uint32_t. (Con le ABI odierne questo è estremamente più probabile che sia un problema per uint16_t.)
zwol,

9
1 °, grazie per una risposta dettagliata. Ma: Oh caro. Il tuo lungo paragrafo: " longsarà di solito il tipo più ampio che può essere gestito con le istruzioni della singola macchina. ..." - e questo è esattamente sbagliato . Guarda il modello di dati di Windows. IMHO, l'intero esempio che segue si rompe, perché su x64 Windows è lungo a 32 bit.
Martin Ba,

6

Un'altra risposta elabora già i tipi di cstdint e le varianti meno conosciute al loro interno.

Vorrei aggiungere a questo:

utilizzare nomi di tipi specifici del dominio

Cioè, non dichiarare i parametri e le variabili uint32_t(certamente no long!), Ma nomi come channel_id_type, room_count_typeecc.

sulle biblioteche

Librerie di terze parti che usano longo no possono essere fastidiose, specialmente se usate come riferimenti o puntatori a quelle.

La cosa migliore è fare involucri.

Qual è la mia strategia, in generale, è quella di creare una serie di funzioni simil-cast che verranno utilizzate. Sono sovraccarichi per accettare solo quei tipi che corrispondono esattamente ai tipi corrispondenti, insieme a qualsiasi variazione del puntatore ecc. Di cui hai bisogno. Sono definiti in modo specifico per os / compilatore / impostazioni. Ciò ti consente di rimuovere gli avvisi e tuttavia garantire che vengano utilizzate solo le conversioni "giuste".

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

In particolare, con tipi primitivi diversi che producono 32 bit, la scelta di come int32_tviene definita potrebbe non corrispondere alla chiamata della libreria (ad es. Int vs long su Windows).

La funzione simile a cast documenta lo scontro, prevede il controllo in fase di compilazione sul risultato corrispondente al parametro della funzione e rimuove qualsiasi avviso o errore se e solo se il tipo effettivo corrisponde alla dimensione reale coinvolta. Cioè, è sovraccarico e definito se passo (su Windows) an int*o a long*e dà un errore di compilazione in caso contrario.

Quindi, se la libreria viene aggiornata o qualcuno cambia ciò che channel_id_typeè, questo continua a essere verificato.


perché il downvote (senza commento)?
JDługosz,

Perché la maggior parte dei voti negativi su questa rete appare senza commenti ...
Ruslan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.