Che cos'è size_t in C?


626

Mi sto confondendo con size_tin C. So che è stato restituito sizeofdall'operatore. Ma cos'è esattamente? È un tipo di dati?

Diciamo che ho un forciclo:

for(i = 0; i < some_size; i++)

Dovrei usare int i;o size_t i;?


11
Se quelle sono le tue uniche opzioni, usa intif some_sizeè firmata, size_tse non è firmata.
Nate,

8
@Nate Quello non è corretto. POSIX ha un tipo ssize_t ma il tipo effettivamente corretto da usare è ptrdiff_t.
Steven Stewart-Gallus,

2
Le risposte non sono chiare come nella programmazione di basso livello: C, Assembly ed esecuzione del programma su Intel® 64 . Come indicato nel libro, l'utilizzo di un indice int ipotrebbe non essere sufficiente per indirizzare un array enorme. Quindi usando size_t ipuoi indirizzare più indici, quindi anche se hai un array enorme che non dovrebbe essere un problema. size_tè un tipo di dati: di solito un unsigned long intma questo dipende dal tuo sistema.
bruno

Risposte:


461

Da Wikipedia :

Secondo lo standard ISO C del 1999 (C99), size_tè un tipo intero senza segno di almeno 16 bit (vedere sezioni 7.17 e 7.18.3).

size_tè un tipo di dati senza segno definito da diversi standard C / C ++, ad esempio lo standard C99 ISO / IEC 9899, ​​definito in stddef.h. 1 Può essere ulteriormente importato includendo stdlib.hcome questo file internamente include stddef.h.

Questo tipo viene utilizzato per rappresentare la dimensione di un oggetto. Le funzioni di libreria che accettano o restituiscono dimensioni si aspettano che siano di tipo o abbiano il tipo restituito di size_t. Inoltre, la dimensione dell'operatore basata sul compilatore più frequentemente utilizzata dovrebbe valutare un valore costante compatibile con size_t.

Come conseguenza, size_tè un tipo garantito per contenere qualsiasi indice di array.


4
"Le funzioni di libreria che accettano o restituiscono dimensioni si aspettano che siano di tipo ... size_t" Ad eccezione del fatto che stat () utilizza off_t per la dimensione di un file
Draemon,

64
@Draemon Quel commento riflette una confusione fondamentale. size_tè per gli oggetti in memoria. Lo standard C non ha nemmeno definire stat()o off_t(quelle sono le definizioni POSIX) o nulla a che fare con i dischi o file system - esso si ferma a FILEcorsi d'acqua. La gestione della memoria virtuale è completamente diversa dai file system e dalla gestione dei file per quanto riguarda i requisiti di dimensione, quindi menzionare off_tqui è irrilevante.
jw013,

3
@ jw013: difficilmente la definirei una confusione fondamentale, ma fai un punto interessante. Tuttavia, il testo tra virgolette non dice "dimensioni degli oggetti in memoria" e "offset" non è certo un buon nome per un tipo di dimensione, indipendentemente da dove si trova.
Draemon,

30
@Draemon Un buon punto. Questa risposta cita Wikipedia, che in questo caso non ha la migliore spiegazione, secondo me. Lo stesso standard C è molto più chiaro: definisce size_tcome il tipo del risultato sizeofdell'operatore (7,17p2 circa <stddef.h>). La sezione 6.5 spiega esattamente come funzionano le espressioni C (6.5.3.4 per sizeof). Poiché non è possibile applicare sizeofa un file su disco (principalmente perché C non definisce nemmeno il funzionamento di dischi e file), non c'è spazio per la confusione. In altre parole, incolpare Wikipedia (e questa risposta per citare Wikipedia e non l'attuale standard C).
jw013,

2
@Draemon - Concordo anche con la valutazione della "confusione fondamentale". Se non hai letto gli standard C / C ++, potresti pensare che "oggetto" si riferisca a "programmazione orientata agli oggetti", cosa che non accade. Leggi lo standard C, che non ha nessuno di quegli oggetti OOP, ma ha ancora degli oggetti, e scoprilo. La risposta potrebbe sorprenderti!
Heath Hunnicutt,

220

size_tè un tipo senza segno. Pertanto, non può rappresentare alcun valore negativo (<0). Lo usi quando stai contando qualcosa e sei sicuro che non può essere negativo. Ad esempio, strlen()restituisce a size_tperché la lunghezza di una stringa deve essere almeno 0.

Nel tuo esempio, se l'indice del tuo ciclo sarà sempre maggiore di 0, potrebbe avere senso utilizzare size_to qualsiasi altro tipo di dati senza segno.

Quando usi un size_toggetto, devi assicurarti che in tutti i contesti in cui viene usato, incluso l'aritmetica, desideri valori non negativi. Ad esempio, supponiamo che tu abbia:

size_t s1 = strlen(str1);
size_t s2 = strlen(str2);

e vuoi trovare la differenza delle lunghezze di str2e str1. Non puoi fare:

int diff = s2 - s1; /* bad */

Questo perché il valore assegnato diffsarà sempre un numero positivo, anche quando s2 < s1, poiché il calcolo viene eseguito con tipi senza segno. In questo caso, a seconda del caso d'uso, potrebbe essere meglio usare int(o long long) per s1e s2.

Ci sono alcune funzioni in C / POSIX che potrebbero / dovrebbero usare size_t, ma non per ragioni storiche. Ad esempio, il secondo parametro fgetsdovrebbe idealmente essere size_t, ma lo è int.


8
@Alok: Due domande: 1) qual è la dimensione di size_t? 2) perché dovrei preferire size_tqualcosa del genere unsigned int?
Lazer,

2
@Lazer: la dimensione di size_tè sizeof(size_t). Lo standard C garantisce che SIZE_MAXsarà almeno 65535. size_tè il tipo restituito sizeofdall'operatore e viene utilizzato nella libreria standard (ad esempio strlenrestituisce size_t). Come ha detto Brendan, size_tnon è necessario che sia lo stesso unsigned int.
Alok Singhal,

4
@Lazer: sì, size_tè garantito che sia un tipo senza segno.
Alok Singhal,

2
@Celeritas no, intendo che un tipo senza segno può rappresentare solo valori non negativi. Probabilmente avrei dovuto dire "Non può rappresentare valori negativi".
Alok Singhal,

4
@JasonOster, il complemento a due non è un requisito nello standard C. Se il valore di s2 - s1overflow è an int, il comportamento non è definito.
Alok Singhal,

73

size_t è un tipo che può contenere qualsiasi indice di array.

A seconda dell'implementazione, può essere uno dei seguenti:

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

Ecco come size_tviene definito nella stddef.hmia macchina:

typedef unsigned long size_t;

4
Certamente typedef unsigned long size_tdipende dal compilatore. O stai suggerendo che sia sempre così?
chux - Ripristina Monica il

4
@chux: In effetti, solo perché un'implementazione lo definisce come tale non significa che tutti lo facciano. Caso in questione: Windows a 64 bit. unsigned longè a 32 bit, size_tè a 64 bit.
Tim Čas,

2
qual è esattamente lo scopo di size_t? Quando posso creare una variabile per me come: "int mysize_t;" o "long mysize_t" o "unsigned long mysize_t". Perché qualcuno dovrebbe aver creato questa variabile per me?
midkin,

1
@midkin size_tnon è una variabile. È un tipo che puoi usare quando vuoi rappresentare la dimensione di un oggetto in memoria.
Arjun Sreedharan,

1
è vero che size_tè sempre a 32 bit su una macchina a 32 bit, allo stesso modo a 64 bit?
John Wu,

70

Se sei un tipo empirico ,

echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Uscita per Ubuntu 14.04 64-bit GCC 4.8:

typedef long unsigned int size_t;

Si noti che stddef.hè fornito da GCC e non glibc src/gcc/ginclude/stddef.hin GCC 4.2.

Interessanti apparizioni C99

  • mallocprende size_tcome argomento, quindi determina la dimensione massima che può essere allocata.

    E poiché viene anche restituito da sizeof, penso che limiti la dimensione massima di qualsiasi array.

    Vedi anche: Qual è la dimensione massima di un array in C?


1
Ho lo stesso ambiente, tuttavia, l'ho testato per 32 bit, passando l'opzione "-m32" del GCC, il risultato è stato: "typedef unsigned int size_t". Grazie per aver condiviso questo fantastico comando @Ciro, mi ha aiutato molto! :-)
silvioprog,

2
La questione in sé non è confusa. È la mente confusa che cerca di porre molte domande e dare molte risposte. Sono sorpreso che questa risposta e quella di Arjun Sreedharan non impediscano ancora alle persone di chiedere e rispondere.
biocyberman

1
Grande risposta, perché in realtà ti dice che cosa size_tè , almeno su un popolare distro Linux.
Andrey Portnoy,


19

Poiché nessuno lo ha ancora menzionato, il significato linguistico primario di size_tè che l' sizeofoperatore restituisce un valore di quel tipo. Allo stesso modo, il significato principale di ptrdiff_tè che sottraendo un puntatore da un altro si otterrà un valore di quel tipo. Le funzioni di libreria che lo accettano lo fanno perché consentiranno a tali funzioni di lavorare con oggetti le cui dimensioni superano UINT_MAX su sistemi in cui tali oggetti potrebbero esistere, senza forzare i chiamanti a sprecare codice che passa un valore maggiore di "unsigned int" su sistemi in cui il tipo più grande basterebbe per tutti gli oggetti possibili.


La mia domanda è sempre stata: se sizeof non è mai esistito, ci sarebbe bisogno di size_t?
Decano P

@DeanP: Forse no, anche se ci sarebbe poi una domanda su quale tipo di argomento dovrebbe essere usato per cose del genere malloc(). Personalmente, mi sarebbe piaciuto avere versioni visto che accettano argomenti di tipo int, longe long long, con alcune implementazioni che promuovono i tipi più brevi e altri di attuazione per esempio lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}[su alcune piattaforme, chiamando imalloc(123)sarebbe più conveniente di chiamare lmalloc(123);, e anche su una piattaforma dove size_tè 16 bit, codice che vuole allocare dimensioni calcolate in un valore `lungo` ...
supercat

... dovrebbe poter fare affidamento sull'allocazione non riuscita se il valore è maggiore di quello che l'allocatore può gestire.
supercat

11

Per size_tcapire perché doveva esistere e come siamo arrivati ​​qui:

In termini pragmatici, size_te ptrdiff_tsono garantiti avere 64 bit di larghezza su un'implementazione a 64 bit, 32 bit di larghezza su un'implementazione a 32 bit e così via. Non potevano forzare alcun tipo esistente a significare che, su ogni compilatore, senza rompere il codice legacy.

Un size_to ptrdiff_tnon è necessariamente uguale a un intptr_to uintptr_t. Erano diversi su alcune architetture che erano ancora in uso quando size_te ptrdiff_tfurono aggiunte allo Standard alla fine degli anni '80, e divennero obsolete quando C99 aggiunse molti nuovi tipi ma non se ne andarono ancora (come Windows a 16 bit). L'x86 in modalità protetta a 16 bit aveva una memoria segmentata in cui l'array o la struttura più grande possibile poteva avere solo 65.536 byte di dimensione, ma un farpuntatore doveva essere largo 32 bit, più largo dei registri. Su quelli, intptr_tsarebbe stato largo 32 bit ma size_teptrdiff_tpotrebbe essere largo 16 bit e inserito in un registro. E chi sapeva che tipo di sistema operativo potrebbe essere scritto in futuro? In teoria, l'architettura i386 offre un modello di segmentazione a 32 bit con puntatori a 48 bit che nessun sistema operativo ha mai effettivamente utilizzato.

Il tipo di offset della memoria non potrebbe essere longdovuto al fatto che troppi codici legacy presuppongono una longlarghezza esattamente di 32 bit. Questo presupposto è stato persino integrato nelle API UNIX e Windows. Sfortunatamente, molti altri codici legacy presumevano anche che a longfosse abbastanza largo da contenere un puntatore, un offset di file, il numero di secondi trascorsi dal 1970 e così via. POSIX ora fornisce un modo standardizzato per forzare la seconda ipotesi a essere vera invece della prima, ma nessuna delle due è una ipotesi portatile da fare.

Non potrebbe essere intperché solo una manciata di compilatori negli anni '90 ha reso intlarga 64 bit. Quindi sono diventati davvero strani mantenendo long32 bit di larghezza. La prossima revisione dello standard ha dichiarato illegale intessere più ampia di long, ma intè ancora larga 32 bit sulla maggior parte dei sistemi a 64 bit.

Non potrebbe essere long long int, che comunque è stato aggiunto in seguito, poiché è stato creato per essere largo almeno 64 bit anche su sistemi a 32 bit.

Quindi, era necessario un nuovo tipo. Anche se non lo fosse, tutti quegli altri tipi significavano qualcosa di diverso da un offset all'interno di un array o di un oggetto. E se c'era una lezione dal fiasco della migrazione da 32 a 64 bit, doveva essere specifica su quali proprietà un tipo avesse bisogno e non usarne una che significasse cose diverse in programmi diversi.


Non sono d' accordo con " size_te ptrdiff_tsono garantiti essere larghi 64 bit su un'implementazione a 64 bit", ecc. La garanzia è sopravvalutata. La gamma di size_tè principalmente determinata dalla capacità di memoria dell'implementazione. "un'implementazione n-bit" è principalmente la larghezza del processore nativo degli interi. Certamente molte implementazioni usano una memoria di dimensioni simili e una larghezza del bus del processore, ma esistono interi nativi ampi con memoria scarsa o processori stretti con molta memoria che separano queste due proprietà di implementazione.
chux - Ripristina Monica il

8

size_te intnon sono intercambiabili. Ad esempio su Linux a size_t64 bit ha una dimensione di 64 bit (ovvero sizeof(void*)) ma intè a 32 bit.

Si noti inoltre che size_tnon è firmato. Se hai bisogno di una versione firmata, ci sono ssize_tsu alcune piattaforme e sarebbe più rilevante per il tuo esempio.

Come regola generale, suggerirei di utilizzare intper la maggior parte dei casi generali e usare solo size_t/ ssize_tquando ce n'è una necessità specifica ( mmap()ad esempio).


3

In generale, se inizi da 0 e vai verso l'alto, usa sempre un tipo senza segno per evitare un overflow che ti porta in una situazione di valore negativo. Ciò è di fondamentale importanza, perché se i limiti dell'array risultano essere inferiori al massimo del loop, ma il massimo del loop risulta essere maggiore del massimo del tipo, verrà visualizzato un dato negativo e si potrebbe verificare un errore di segmentazione (SIGSEGV ). Quindi, in generale, non usare mai int per un ciclo che inizia da 0 e va verso l'alto. Usa un unsigned.


3
Non posso accettare le tue argomentazioni. Dici che è meglio che il bug di overflow porti silenziosamente ad accedere a dati validi nel tuo array?
maf-soft,

1
@ maf-soft è corretto. se l'errore non viene rilevato, lo rende peggiore di un arresto anomalo del programma. perché questa risposta ha ottenuto voti?
yoyo_fun

Se accede a dati validi nell'array, non è un bug perché il tipo senza segno non trabocca al limite con il tipo firmato. Cos'è questa logica ragazzi? Diciamo per qualche motivo che usi char per scorrere su un array di 256 elementi ... firmato traboccerà a 127 e 128 ° elemento sarà sigsegv, ma se usi unsigned, passerà attraverso l'intero array come previsto. Poi di nuovo, quando stai usando un int, i tuoi array non saranno realmente più grandi di 2 miliardi di elementi, quindi in entrambi i casi non importa ...
Purple Ice,

1
Non riesco a immaginare alcuna situazione in cui l'overflow di numeri interi non sia un bug, sia che si avvolga in positivo o in negativo. Solo perché non ottieni un segfault non significa che vedi un comportamento corretto! E puoi riscontrare un errore di segmentazione o meno, sia che l'offset sia positivo o negativo; tutto dipende dal layout della memoria. @PurpleIce, non penso che tu stia dicendo la stessa cosa di questa risposta; il tuo argomento sembra essere che dovresti scegliere un tipo di dati abbastanza grande da contenere il valore più grande che vuoi inserire, il che è semplicemente buon senso.
Soren Bjornstad,

Detto questo, preferisco usare un tipo senza segno per gli indici di loop semanticamente ; se la tua variabile non sarà mai negativa, potresti anche indicarla nel tipo che scegli. Potrebbe anche consentire al compilatore di individuare un bug in cui il valore è risultato negativo, sebbene GCC almeno sia abbastanza terribile nello scovare questo particolare errore (in un'occasione ho inizializzato un unsigned a -1 e non ho ricevuto un avviso). Allo stesso modo, size_t è semanticamente appropriato per gli indici di array.
Soren Bjornstad,

3

size_t è un tipo di dati intero senza segno. Sui sistemi che usano la libreria GNU C, questo sarà unsigned int o unsigned long int. size_t è comunemente usato per l'indicizzazione dell'array e il conteggio dei cicli.


1

size_t o qualsiasi tipo senza segno potrebbe essere visto usato come variabile loop poiché le variabili loop sono generalmente maggiori o uguali a 0.

Quando utilizziamo un oggetto size_t , dobbiamo assicurarci che in tutti i contesti in cui viene utilizzato, incluso l'aritmetica, desideriamo solo valori non negativi. Ad esempio, il seguente programma darebbe sicuramente il risultato inaspettato:

// C program to demonstrate that size_t or
// any unsigned int type should be used 
// carefully when used in a loop

#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];

// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;

// But reverse cycles are tricky for unsigned 
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}

Output
Infinite loop and then segmentation fault

1

size_tè un tipo di dati intero senza segno che può assegnare solo 0 e valori di numero intero superiori a 0. Misura byte di qualsiasi dimensione dell'oggetto e restituiti sizeofdall'operatore. constè la rappresentazione della sintassi di size_t, ma senza di constte puoi eseguire il programma.

const size_t number;

size_tusato regolarmente per l'indicizzazione dell'array e il conteggio dei loop. Se il compilatore 32-bitfunziona, funzionerebbe unsigned int. Se il compilatore 64-bitfunziona, funzionerebbe unsigned long long intanche su . C'è per la dimensione massima di a size_tseconda del tipo di compilatore.

size_tgià definire il <stdio.h>file di intestazione, ma può anche definire con <stddef.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>intestazioni.

  • Esempio (con const)
#include <stdio.h>

int main()
{
    const size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Produzione -: size = 800


  • Esempio (senza const)
#include <stdio.h>

int main()
{
    size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Produzione -: size = 800


-3

Da quanto ho capito, size_tè un unsignednumero intero la cui dimensione in bit è abbastanza grande da contenere un puntatore all'architettura nativa.

Così:

sizeof(size_t) >= sizeof(void*)

16
Non vero. La dimensione del puntatore può essere maggiore di size_t. Diversi esempi: i compilatori C in modalità reale x86 possono avere 32 bit FARo HUGEpuntatori ma size_t è ancora 16 bit. Un altro esempio: Watcom C aveva un puntatore grasso speciale per memoria estesa larga 48 bit, ma size_tnon lo era. Sul controller incorporato con architettura Harvard, non hai alcuna correlazione, poiché entrambi riguardano spazi di indirizzi diversi.
Patrick Schlüter,

1
E su quello stackoverflow.com/questions/1572099/… ci sono altri esempi AS / 400 con puntatori a 128 bit e 32 bitsize_t
Patrick Schlüter,

Questo è palesemente falso. Tuttavia, continuiamo qui
Antti Haapala,
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.