2009-09-23 17 views
218

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

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

Allora nel C99, lo standard introdotto i intptr_t e uintptr_t tipi, che sono firmati e non firmati tipi garantiti per essere in grado di tenere i puntatori:

uintptr_t p = (size_t) v; 

Allora, qual è la differenza tra l'utilizzo size_t e uintptr_t? Entrambi sono senza segno, ed entrambi dovrebbero essere in grado di contenere qualsiasi tipo di puntatore, in modo che sembrino funzionalmente identici. C'è qualche ragione convincente per usare (o meglio ancora, uno void *) piuttosto che uno size_t, diverso dalla chiarezza? In una struttura opaca, dove il campo sarà gestito solo da funzioni interne, c'è qualche ragione per non farlo?

Allo stesso modo, ptrdiff_t è stato un tipo firmato in grado di contenere le differenze di puntatore e quindi in grado di contenere la maggior parte dei puntatori, quindi come è distinto da intptr_t?

Non tutti questi tipi servono fondamentalmente versioni diverse della stessa funzione? Se no, perché? Cosa non posso fare con uno di loro che non posso fare con un altro? Se è così, perché C99 ha aggiunto due tipi essenzialmente superflui alla lingua?

Sono disposto a ignorare i puntatori di funzione, in quanto non si applicano al problema corrente, ma sentitevi liberi di menzionarli, poiché ho un sospetto furtivo che saranno al centro della risposta "corretta".

risposta

210

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

Non necessariamente! Per esempio, riprendi i tempi delle architetture segmentate a 16 bit: un array potrebbe essere limitato a un singolo segmento (quindi dovrebbe essere un size_t a 16 bit) MA potresti avere più segmenti (quindi sarebbe necessario un tipo a 32 bit intptr_t per selezionare il segmento e l'offset al suo interno). So che queste cose sembrano strane in questi giorni di architetture unsegmentate uniformemente indirizzabili, ma lo standard DEVE soddisfare una varietà più ampia di "cosa è normale nel 2009", lo sai! -)

+4

Questo, insieme ai numerosi altri che sono saltati alla stessa conclusione, spiega la differenza tra 'size_t' e' uintptr_t', ma per quanto riguarda 'ptrdiff_t' e' intptr_t' - entrambi non sarebbero in grado di memorizzare lo stesso intervallo di valori su quasi tutte le piattaforme? Perché hanno entrambi i tipi di numeri interi con segno e senza segno del puntatore, in particolare se 'ptrdiff_t' serve già allo scopo di un tipo di intero con segno del puntatore. –

+7

La frase chiave è "on * quasi * qualsiasi 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

+22

@Chris, solo per i puntatori _in uno stesso array_ è ben definito per fare la differenza. Quindi, su esattamente le stesse architetture segmentate a 16 bit (l'array deve vivere all'interno di un singolo segmento ma due diversi array possono essere in segmenti diversi) i puntatori devono essere 4 byte ma il puntatore ** differenze ** potrebbe essere 2 byte! –

11

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

Mentre questi non sono più comunemente utilizzati nelle macchine desktop, lo standard C è progettato per supportare anche architetture specializzate di piccole dimensioni. Ad esempio, sono ancora in fase di sviluppo sistemi embedded con CPU a 8 o 16 bit.

+0

Ma puoi indicizzare i puntatori come gli array, quindi anche "size_t" dovrebbe essere in grado di gestirlo? Oppure gli array dinamici in qualche segmento lontano sarebbero ancora limitati all'indicizzazione all'interno del loro segmento? –

+0

I puntatori di indicizzazione sono supportati solo tecnicamente dalle dimensioni della matrice 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 modello di memoria "enorme", in cui i puntatori (puntatori a 32 bit segmentati) venivano manipolati in modo da poter indirizzare l'intera memoria come un unico array, ma l'aritmetica applicata ai puntatori dietro le quinte era piuttosto brutto - quando l'offset è passato oltre un valore di 16 (o qualcosa), l'offset è stato ricollocato su 0 e la parte del segmento è stata incrementata. –

+5

Leggi http://en.wikipedia.org/wiki/C_memory_model#Memory_segmentation e piangi i programmatori MS-DOS che sono morti per poter essere liberi. – Justicle

4

Immagino (e questo vale per tutti nomi di tipi) che trasmetta meglio le tue intenzioni nel codice.

Per esempio, anche se unsigned short e wchar_t sono le stesse dimensioni su Windows (credo), utilizzando wchar_t invece di unsigned short mostra l'intenzione che si intende utilizzare per memorizzare un carattere esteso, piuttosto che solo qualche numero arbitrario.

+0

Ma c'è una differenza qui - sul mio sistema, 'wchar_t' è molto più grande di un' corto senza segno 'quindi usarne uno per l'altro sarebbe errato e creare un serio (e moderno) problema di portabilità, mentre la portabilità riguarda tra 'size_t 'e' uintptr_t' sembrano giacere nelle terre lontane del 1980-qualcosa (pugnalata al buio al momento, lì) –

+0

Touché! Ma ancora, 'size_t' e' uintptr_t' hanno ancora implicazioni nel loro nome. – dreamlax

+0

Lo fanno, e volevo sapere se c'era una motivazione per questo oltre alla semplice chiarezza. E si scopre che c'è. –

78

Per quanto riguarda la sua dichiarazione:

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

Questo è definito un errore, un equivoco derivante da un ragionamento errato. Si può pensare quest'ultimo segue dal primo ma non è necessariamente così.

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

C99 indica che il limite superiore di una variabile size_t è definito da SIZE_MAX e che può essere basso come 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 troverai che la tua ipotesi vale, ma non è perché lo standard lo garantisce. Perché in realtà lo non lo garantisce da.

+0

Invece di ridigitare quello che ho detto nei commenti per Alex Martelli, dirò semplicemente grazie per il chiarimento, ma ribadisco la seconda parte della mia domanda (la parte 'ptrdiff_t' vs.' intptr_t'). –

+0

Questo chiarimento mi ha aiutato. Grazie! – zelgit

2

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 anche di prevedere tutti i possibili sistemi futuri.

Così sicuro, il modo in cui le cose si sono sistemate, fino ad ora 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, dove l'uso di un tipo a 64 bit completo è costoso e potrebbe voler puntare su operazioni di I/O più grandi di 4 GB ma avere ancora puntatori a 64 bit.

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

Inoltre, guarda cosa esisteva in quel momento. Oltre ai zillion 286 modelli di memoria in modalità reale, che dire dei mainframe dei puntatori a 18 bit/word a 18 bit CDC? 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 cerchi di coprire tutte le basi ...

31

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

Non è la semplice differenza nei nomi motivo sufficiente per utilizzare il tipo corretto per la cosa corretta?

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

In caso contrario, è possibile utilizzare solo unsigned long (o, in questi tempi moderni, unsigned long long) per tutto. Le dimensioni non sono tutto, i nomi dei tipi contengono un significato che è utile poiché aiuta a descrivere il programma.

+0

Sono d'accordo, ma stavo considerando una sorta di trucco/trucco (che chiaramente documenterei, ovviamente) che implica la memorizzazione di un tipo di puntatore in un campo 'size_t'. –

+0

La ragione per non essere portabilità. Dallo standard C99 per 'intptr_t' e' uintptr_t': _ "Questi tipi sono opzionali." _ –

-8
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 deve sempre sostituire size_t e viceversa.

+7

Tutto ciò che mostra è una particolare eccitazione della sintassi di C. L'indicizzazione di matrice è definita in termini di x [y] essendo equivalente a * (x + y), e poiché un + 3 e 3 + a sono identici per tipo e valore, puoi usare 3 [a] o [3]. –