size_t vs. uintptr_t


246

Lo standard C garantisce che size_tè un tipo che può contenere qualsiasi indice di array. Ciò significa che, logicamente, size_tdovrebbe essere in grado di contenere qualsiasi tipo di puntatore. Ho letto su alcuni siti che ho trovato su Google che questo è legale e / o dovrebbe sempre funzionare:

void *v = malloc(10);
size_t s = (size_t) v;

Quindi, in C99, lo standard ha introdotto i tipi intptr_te uintptr_t, che sono tipi firmati e non firmati garantiti per essere in grado di contenere i puntatori:

uintptr_t p = (size_t) v;

Quindi qual è la differenza tra l'utilizzo di size_te uintptr_t? Entrambi sono senza segno ed entrambi dovrebbero essere in grado di contenere qualsiasi tipo di puntatore, quindi sembrano funzionalmente identici. C'è qualche vero motivo convincente da usare uintptr_t(o meglio ancora, a void *) piuttosto che a size_t, oltre alla chiarezza? In una struttura opaca, in cui il campo sarà gestito solo da funzioni interne, c'è qualche motivo per non farlo?

Allo stesso modo, ptrdiff_tè stato un tipo con segno in grado di contenere le differenze di puntatore e quindi in grado di contenere la maggior parte di qualsiasi puntatore, quindi come si distingue intptr_t?

Tutti questi tipi non servono sostanzialmente versioni banalmente diverse della stessa funzione? Se no, perché? Cosa non posso fare con uno di loro che non posso fare con un altro? In tal caso, perché C99 ha aggiunto due tipi essenzialmente superflui alla lingua?

Sono disposto a ignorare i puntatori alle funzioni, poiché non si applicano al problema attuale, ma sono libero di menzionarli, poiché ho il sospetto che saranno fondamentali per la risposta "corretta".

Risposte:


236

size_tè un tipo che può contenere qualsiasi indice di array. Ciò significa che, logicamente, size_t dovrebbe essere in grado di contenere qualsiasi tipo di puntatore

Non necessariamente! Torniamo ai giorni delle architetture segmentate a 16 bit per esempio: un array potrebbe essere limitato a un singolo segmento (quindi un 16 bit size_tfarebbe) MA potresti avere più segmenti (quindi intptr_tsarebbe necessario un tipo a 32 bit per scegliere il segmento e l'offset al suo interno). So che queste cose suonano strane in questi giorni di architetture non segmentate indirizzabili in modo uniforme, ma lo standard DEVE soddisfare una varietà più ampia di "ciò che è normale nel 2009", lo sai! -)


6
Questo, insieme ai numerosi altri che sono passati alla stessa conclusione, spiega la differenza tra size_te uintptr_tma che dire di ptrdiff_te intptr_t- entrambi non sarebbero in grado di memorizzare lo stesso intervallo di valori su quasi tutte le piattaforme? Perché avere tipi interi di dimensioni puntatore sia firmate che non firmate, in particolare se ptrdiff_tserve già allo scopo di un tipo intero di dimensioni puntatore firmato.
Chris Lutz,

8
Frase chiave c'è "su quasi ogni piattaforma", @Chris. Un'implementazione è libera di limitare i puntatori all'intervallo 0xf000-0xffff - ciò richiede un intptr_t a 16 bit ma solo un ptrdiff_t a 12/13 bit.
paxdiablo,

29
@Chris, solo per i puntatori all'interno dello stesso array è ben definito fare la differenza. Quindi, esattamente sulle stesse architetture segmentate a 16 bit (l'array deve risiedere all'interno di un singolo segmento ma due array diversi possono trovarsi in segmenti diversi) i puntatori devono essere di 4 byte ma le differenze di puntatore potrebbero essere di 2 byte!
Alex Martelli,

6
@AlexMartelli: Tranne che le differenze del puntatore possono essere positive o negative. Lo standard richiede size_tche siano almeno 16 bit, ma ptrdiff_tche siano almeno 17 bit (che in pratica significa che probabilmente saranno almeno 32 bit).
Keith Thompson,

3
Non importa le architetture segmentate, che dire di un'architettura moderna come x86-64? Le prime implementazioni di questa architettura offrono solo uno spazio indirizzabile a 48 bit, ma i puntatori stessi sono un tipo di dati a 64 bit. Il più grande blocco contiguo di memoria che potresti ragionevolmente affrontare sarebbe di 48 bit, quindi devo immaginare SIZE_MAXche non dovrebbe essere 2 ** 64. Questo sta usando l'indirizzamento semplice, intendiamoci; non è necessaria alcuna segmentazione per avere una discrepanza tra SIZE_MAXe l'intervallo di un puntatore di dati.
Andon M. Coleman,

89

Per quanto riguarda la tua dichiarazione:

"Lo standard C garantisce che size_tè un tipo che può contenere qualsiasi indice di array. Ciò significa che, logicamente, size_tdovrebbe essere in grado di contenere qualsiasi tipo di puntatore."

Questo è in realtà un errore (un equivoco derivante da un ragionamento errato) (a) . Potresti pensare che il secondo derivi dal primo, ma in realtà non è così.

Puntatori e indici di array non sono la stessa cosa. È abbastanza plausibile prevedere un'implementazione conforme che limiti gli array a 65536 elementi ma consente ai puntatori di indirizzare qualsiasi valore in un enorme spazio di indirizzi a 128 bit.

C99 afferma che il limite superiore di una size_tvariabile è definito da SIZE_MAXe questo può essere fino a 65535 (vedere C99 TR3, 7.18.3, invariato in C11). I puntatori sarebbero abbastanza limitati se fossero limitati a questa gamma nei sistemi moderni.

In pratica, probabilmente scoprirai che il tuo presupposto è valido, ma non perché lo standard lo garantisca. Perché in realtà non lo garantisce.


(a) A proposito, questa non è una forma di attacco personale, affermando semplicemente perché le tue affermazioni sono errate nel contesto del pensiero critico. Ad esempio, anche il seguente ragionamento non è valido:

Tutti i cuccioli sono carini. Questa cosa è carina Quindi questa cosa deve essere un cucciolo.

La carineria o meno del puppiess non ha rilevanza qui, tutto ciò che sto affermando è che i due fatti non portano alla conclusione, perché le prime due frasi consentono l'esistenza di cose carine che non sono cuccioli.

Questo è simile alla tua prima affermazione che non richiede necessariamente la seconda.


Piuttosto che ridigitare ciò che ho detto nei commenti per Alex Martelli, dirò solo grazie per il chiarimento, ma ribadisco la seconda metà della mia domanda (la parte ptrdiff_tvs. intptr_t).
Chris Lutz,

5
@Ivan, come con la maggior parte delle comunicazioni, deve esserci una comprensione condivisa di alcuni elementi di base. Se vedi questa risposta come "scherzosa", ti assicuro che è un fraintendimento del mio intento. Supponendo che ti riferisca al mio commento di "errore logico" (non riesco a vedere altre possibilità), intendevo come una dichiarazione fattuale, non una dichiarazione fatta a spese del PO. Se desideri suggerire alcuni miglioramenti concreti per ridurre al minimo la possibilità di incomprensioni (piuttosto che solo una lamentela generale), sarei felice di prendere in considerazione.
paxdiablo,

1
@ivan_pozdeev - questa è una coppia di modifiche odiose e drastiche, e non vedo alcuna prova che Paxdiablo si stia "prendendo in giro" per nessuno. Se fossi il PO, tornerei indietro ...
ex nihilo,

1
@Ivan, non ero davvero contento delle modifiche che hai proposto, sono tornate indietro e hanno anche provato a rimuovere qualsiasi offesa involontaria. Se hai altre modifiche da offrire, ti suggerisco di avviare una chat in modo che possiamo discutere.
paxdiablo,

1
@paxdiablo okay, immagino che "questo è in realtà un errore" è meno condiscendente.
ivan_pozdeev,

36

Lascerò tutte le altre risposte autonome per quanto riguarda il ragionamento con limiti di segmento, architetture esotiche e così via.

La semplice differenza nei nomi non è sufficiente per usare il tipo giusto per la cosa giusta?

Se stai memorizzando una dimensione, usa size_t. Se stai memorizzando un puntatore, usa intptr_t. Una persona che legge il tuo codice saprà immediatamente che "ah, questa è una dimensione di qualcosa, probabilmente in byte", e "oh, ecco un valore del puntatore che viene memorizzato come numero intero, per qualche motivo".

Altrimenti, potresti semplicemente usare unsigned long(o, in questi tempi moderni, unsigned long long) per tutto. La dimensione non è tutto, i nomi dei tipi hanno un significato che è utile poiché aiuta a descrivere il programma.


Sono d'accordo, ma stavo prendendo in considerazione una sorta di hack / trucco (che avrei chiaramente documentato, ovviamente) che comportava la memorizzazione di un tipo di puntatore in un size_tcampo.
Chris Lutz,

@MarkAdler Standard non richiede che i puntatori siano rappresentabili come numeri interi: qualsiasi tipo di puntatore può essere convertito in un tipo intero. Ad eccezione di quanto precedentemente specificato, il risultato è definito dall'implementazione. Se il risultato non può essere rappresentato nel tipo intero, il comportamento non è definito. Non è necessario che il risultato sia compreso nell'intervallo di valori di nessun tipo intero. Pertanto, solo void*, intptr_te uintptr_tsono garantiti per essere in grado di rappresentare qualsiasi puntatore ai dati.
Andrew Svietlichnyy,

12

È possibile che la dimensione dell'array più grande sia più piccola di un puntatore. Pensa alle architetture segmentate: i puntatori possono essere a 32 bit, ma un singolo segmento potrebbe essere in grado di indirizzare solo 64 KB (ad esempio la vecchia architettura 8086 in modalità reale).

Sebbene questi non siano più comunemente utilizzati nelle macchine desktop, lo standard C è destinato a supportare anche piccole architetture specializzate. Ad esempio, esistono ancora sistemi embedded sviluppati con CPU a 8 o 16 bit.


Ma puoi indicizzare i puntatori proprio come gli array, quindi dovresti size_tanche essere in grado di gestirlo? O le matrici dinamiche in alcuni segmenti lontani sarebbero ancora limitate all'indicizzazione all'interno del loro segmento?
Chris Lutz,

L'indicizzazione dei puntatori è supportata solo tecnicamente dalla dimensione dell'array a cui puntano, quindi se un array è limitato a una dimensione di 64 KB, è tutto ciò che l'aritmetica del puntatore deve supportare. Tuttavia, i compilatori MS-DOS supportavano un "enorme" modello di memoria, in cui i puntatori lontani (puntatori segmentati a 32 bit) venivano manipolati in modo da poter indirizzare l'intera memoria come un singolo array - ma l'arithemtic fatto ai puntatori dietro le quinte era piuttosto brutto - quando l'offset è aumentato oltre un valore di 16 (o qualcosa del genere), l'offset è stato riportato a 0 e la parte del segmento è stata incrementata.
Michael Burr,

7
Leggi en.wikipedia.org/wiki/C_memory_model#Memory_segmentation e piangi per i programmatori MS-DOS che sono morti in modo da poter essere liberi.
Justicle,

Peggio ancora, la funzione stdlib non si occupava della parola chiave ENORME. 16bit MS-C per tutte strle funzioni e Borland anche per le memfunzioni ( memset, memcpy, memmove). Ciò significava che potresti sovrascrivere parte della memoria quando l'offset traboccava, il che è stato divertente eseguire il debug sulla nostra piattaforma integrata.
Patrick Schlüter,

@Justicle: l'architettura segmentata 8086 non è ben supportata in C, ma non conosco nessun'altra architettura che sia più efficiente nei casi in cui uno spazio di indirizzi di 1 MB è sufficiente ma 64 KB non lo sarebbe. Alcune moderne JVM utilizzano effettivamente l'indirizzamento in modo molto simile alla modalità reale x86, utilizzando lo spostamento dei riferimenti a oggetti a 32 bit a sinistra di 3 bit per generare indirizzi di base di oggetti in uno spazio di indirizzi di 32 GB.
supercat,

5

Immagino (e questo vale per tutti i nomi di tipo) che trasmette meglio le tue intenzioni in codice.

Ad esempio, anche se unsigned shorte wchar_tho le stesse dimensioni su Windows (penso), usando wchar_tinvece di unsigned shortmostrare l'intenzione che lo userete per memorizzare un carattere largo, piuttosto che solo un numero arbitrario.


Ma qui c'è una differenza: sul mio sistema, wchar_tè molto più grande di una unsigned shortcosì l'utilizzo dell'uno per l'altro sarebbe errato e creerebbe una seria (e moderna) preoccupazione per la portabilità, mentre la portabilità riguarda size_te uintptr_tsembra trovarsi nelle terre lontane del 1980-qualcosa (pugnalata casuale al buio alla data, lì)
Chris Lutz,

Touché! Ma poi di nuovo, size_te uintptr_thanno ancora usi impliciti nei loro nomi.
dreamlax,

Lo fanno, e volevo sapere se c'era una motivazione al di là della semplice chiarezza. E si scopre che c'è.
Chris Lutz,

3

Guardando avanti e indietro, e ricordando che varie architetture strane erano sparse per il paesaggio, sono abbastanza sicuro che stessero cercando di avvolgere tutti i sistemi esistenti e fornire anche tutti i possibili sistemi futuri.

Di certo, nel modo in cui le cose si sono sistemate, finora non abbiamo avuto bisogno di così tanti tipi.

Ma anche in LP64, un paradigma piuttosto comune, avevamo bisogno di size_t e ssize_t per l'interfaccia di chiamata di sistema. Si può immaginare un sistema legacy o futuro più vincolato, in cui l'utilizzo di un tipo a 64 bit è costoso e potrebbero voler puntare su operazioni di I / O più grandi di 4 GB ma avere ancora puntatori a 64 bit.

Penso che devi chiederti: cosa potrebbe essere stato sviluppato, cosa potrebbe venire in futuro. (Forse puntatori a livello di sistema distribuito a 128 bit su Internet, ma non più di 64 bit in una chiamata di sistema, o forse anche un limite "legacy" a 32 bit. :-) Immagine che i sistemi legacy potrebbero ottenere nuovi compilatori C. .

Inoltre, guarda cosa esisteva allora. Oltre ai milioni di modelli di memoria in modalità reale 286, che ne dite dei mainframe CDC 60 bit / 18 bit puntatore? Che ne dici della serie Cray? Non importa il normale ILP64, LP64, LLP64. (Ho sempre pensato che Microsoft fosse pretenzioso con LLP64, avrebbe dovuto essere P64.) Posso certamente immaginare un comitato che cerca di coprire tutte le basi ...


-9
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

Implica che intptr_t debba sempre sostituire size_t e viceversa.


10
Tutto ciò mostra una particolare stranezza della sintassi di C. L'indicizzazione dell'array è definita in termini di x [y] equivalente a * (x + y) e poiché a + 3 e 3 + a sono identici per tipo e valore, puoi usa 3 [a] o a [3].
Fred Nurk,
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.