Ich verwechsle mich mit size_t
in C. Ich weiß, dass es vom sizeof
-Operator zurückgegeben wird. Aber was genau ist das? Ist es ein Datentyp?
Nehmen wir an, ich habe eine for
-Schleife:
for(i = 0; i < some_size; i++)
Soll ich int i;
oder size_t i;
verwenden?
Gemäß dem ISO-C-Standard von 1999 (C99),
size_t
ist eine vorzeichenlose Ganzzahl Typ von mindestens 16 Bit (siehe Abschnitte 7.17 und 7.18.3).
size_t
ist ein vorzeichenloser Datentyp definiert durch mehrere C/C++ - Standards, z.B. die Norm C99 ISO/IEC 9899, das ist definiert instddef.h
. 1 Es kann durch Einbeziehung von .__ weiter importiert werden.stdlib.h
als diese Datei intern sub enthältstddef.h
.Dieser Typ wird zur Darstellung von .__ verwendet. Größe eines Objekts. Bibliotheksfunktionen die Take- oder Return-Größen erwarten sie vom Typ sein oder den Rückgabetyp haben von
size_t
. Weiter die meisten häufig Compiler-basierte Operator sizeof sollte eine .__ auswerten. konstanter Wert, der mit .__ kompatibel istsize_t
.
Implizit ist size_t
ein Typ, der garantiert jeden Array-Index enthält.
size_t
ist ein vorzeichenloser Typ. Es kann also keine negativen Werte (<0) darstellen. Sie verwenden es, wenn Sie etwas zählen, und sind sich sicher, dass es nicht negativ sein kann. Zum Beispiel gibt strlen()
einen size_t
zurück, da die Länge einer Zeichenfolge mindestens 0 sein muss.
Wenn Ihr Schleifenindex in Ihrem Beispiel immer größer als 0 sein wird, kann es sinnvoll sein, size_t
oder einen anderen nicht signierten Datentyp zu verwenden.
Wenn Sie ein size_t
-Objekt verwenden, müssen Sie sicherstellen, dass in allen Kontexten, einschließlich der Arithmetik, nicht negative Werte gewünscht werden. Angenommen, Sie haben Folgendes:
size_t s1 = strlen(str1);
size_t s2 = strlen(str2);
und Sie möchten die Differenz der Längen von str2
und str1
ermitteln. Du kannst nicht tun:
int diff = s2 - s1; /* bad */
Dies liegt daran, dass der Wert, der diff
zugewiesen wird, immer eine positive Zahl ist, selbst wenn s2 < s1
, da die Berechnung mit unsignierten Typen erfolgt. In diesem Fall können Sie je nach Anwendungsfall int
(oder long long
) für s1
und s2
verwenden.
Es gibt einige Funktionen in C/POSIX, die size_t
verwenden könnten/sollten, aus historischen Gründen jedoch nicht. Der zweite Parameter für fgets
sollte idealerweise size_t
sein, ist jedoch int
.
size_t
ist ein Typ, der jeden Array-Index aufnehmen kann.
Je nach Implementierung kann es sich um Folgendes handeln:
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
So wird size_t
in stddef.h
meines Rechners definiert:
typedef unsigned long size_t;
Wenn Sie der empirische Typ sind,
echo | gcc -E -xc -include 'stddef.h' - | grep size_t
Ausgabe für Ubuntu 14.04 64-Bit-GCC 4.8:
typedef long unsigned int size_t;
Beachten Sie, dass stddef.h
von GCC bereitgestellt wird und nicht glibc unter src/gcc/ginclude/stddef.h
in GCC 4.2.
Interessante C99-Auftritte
malloc
nimmt size_t
als Argument an, so dass die maximale Größe festgelegt wird, die zugewiesen werden kann.
Und da es auch von sizeof
zurückgegeben wird, schränkt es die maximale Größe eines Arrays ein.
Siehe auch: Wie groß darf ein Array in C maximal sein?
Die Manpage für types.h sagt:
size_t ist ein vorzeichenloser Integer-Typ
Da es bisher noch nicht erwähnt wurde, ist die sprachliche Bedeutung von size_t
vor allem, dass der Operator sizeof
einen Wert dieses Typs zurückgibt. Ebenso ist die wichtigste Bedeutung von ptrdiff_t
, dass das Subtrahieren eines Zeigers von einem anderen einen Wert dieses Typs ergibt. Bibliotheksfunktionen, die dies akzeptieren, tun dies, weil sie es ermöglichen, dass diese Funktionen mit Objekten arbeiten, deren Größe UINT_MAX auf Systemen überschreitet, auf denen solche Objekte vorhanden sein könnten, ohne dass Aufrufer Code zwingen müssen, Code zu verschwenden, der auf Systemen mit dem größeren Typ einen Wert überschreitet, der größer als "unsigned int" ist würde für alle möglichen Objekte ausreichen.
size_t
und int
sind nicht austauschbar. Unter 64-Bit-Linux ist size_t
beispielsweise 64-Bit groß (d. H. sizeof(void*)
), aber int
ist 32-Bit.
Beachten Sie auch, dass size_t
nicht signiert ist. Wenn Sie eine signierte Version benötigen, gibt es auf einigen Plattformen ssize_t
und dies wäre für Ihr Beispiel relevanter.
Generell würde ich empfehlen, int
für die meisten allgemeinen Fälle zu verwenden und size_t
/ssize_t
nur dann zu verwenden, wenn ein bestimmter Bedarf besteht (beispielsweise mit mmap()
).
Um herauszufinden, warum size_t
existieren musste und wie wir hierher gekommen sind:
In pragmatischen Ausdrücken sind size_t
und ptrdiff_t
bei einer 64-Bit-Implementierung garantiert 64 Bit breit, bei einer 32-Bit-Implementierung 32 Bit breit und so weiter. Sie konnten keinen vorhandenen Typ dazu zwingen, dies auf jedem Compiler zu bedeuten, ohne den alten Code zu brechen.
Ein size_t
oder ptrdiff_t
ist nicht unbedingt dasselbe wie ein intptr_t
oder uintptr_t
. Sie unterschieden sich in bestimmten Architekturen, die noch verwendet wurden, als size_t
und ptrdiff_t
in den späten 80er Jahren zum Standard hinzugefügt wurden, und wurden obsolet, als C99 viele neue Typen hinzufügte, aber noch nicht weg war (wie 16-Bit-Windows). Der x86-Speicher im geschützten 16-Bit-Modus hatte einen segmentierten Speicher, in dem das größtmögliche Array oder die größtmögliche Struktur nur 65.536 Byte groß sein konnte, aber ein Zeiger für far
musste 32 Bit breit und breiter als die Register sein. Auf diesen wäre intptr_t
32 Bit breit gewesen, aber size_t
und ptrdiff_t
könnten 16 Bit breit sein und in ein Register passen. Und wer wusste, welches Betriebssystem in Zukunft geschrieben werden könnte? Theoretisch bietet die i386-Architektur ein 32-Bit-Segmentierungsmodell mit 48-Bit-Zeigern, das noch nie von einem Betriebssystem verwendet wurde.
Der Typ eines Speicher-Offsets kann nicht long
sein, da bei viel zu viel Legacy-Code davon ausgegangen wird, dass long
genau 32 Bit breit ist. Diese Annahme wurde sogar in die UNIX- und Windows-APIs integriert. Leider haben auch viele andere ältere Codes davon ausgegangen, dass eine long
breit genug ist, um einen Zeiger, einen Datei-Offset, die Anzahl der seit 1970 verstrichenen Sekunden usw. aufzunehmen. POSIX bietet jetzt eine standardisierte Möglichkeit, um die letztere Annahme als wahr zu erzwingen, aber auch keine tragbare Annahme.
Es könnte nicht int
sein, da nur eine kleine Handvoll Compiler in den 90er Jahren int
mit einer Breite von 64 Bit erstellt hat. Dann wurden sie wirklich komisch, indem sie long
32 Bit breit hielten. Die nächste Überarbeitung des Standards erklärte es für illegal, dass int
breiter als long
ist, aber int
ist auf den meisten 64-Bit-Systemen immer noch 32 Bit breit.
Es konnte sich nicht um long long int
handeln, der ohnehin später hinzugefügt wurde, da er selbst auf 32-Bit-Systemen mit einer Breite von mindestens 64 Bit erstellt wurde.
Also wurde ein neuer Typ benötigt. Selbst wenn dies nicht der Fall wäre, bedeuteten all diese anderen Typen etwas anderes als einen Versatz innerhalb eines Arrays oder Objekts. Und wenn es eine Lektion aus dem Fiasko der 32-zu-64-Bit-Migration gab, dann war es genau zu sagen, welche Eigenschaften ein Typ haben musste, und keine, die verschiedene Dinge in verschiedenen Programmen bedeutete.
Wenn Sie bei 0 beginnen und aufwärts gehen, verwenden Sie im Allgemeinen immer einen vorzeichenlosen Typ, um einen Überlauf zu vermeiden, der Sie in eine Situation mit negativem Wert führt. Dies ist von entscheidender Bedeutung, denn wenn Ihre Array-Grenzen unter dem Maximalwert Ihrer Schleife liegen, der Maximalwert jedoch größer als der Maximalwert Ihres Typs ist, werden Sie negativ umlaufen und es kommt zu einem Segmentierungsfehler (SIGSEGV). Verwenden Sie also im Allgemeinen niemals int für eine Schleife, die bei 0 beginnt und aufwärts geht. Verwenden Sie einen vorzeichenlosen.
size_t ist ein vorzeichenloser Integer-Datentyp. Auf Systemen, die die C-Bibliothek GNU verwenden, ist dies vorzeichenlos int oder unsigned long int. size_t wird üblicherweise für die Array-Indexierung und die Schleifenzählung verwendet.
size_t oder ein beliebiger vorzeichenloser Typ kann als Schleifenvariable verwendet werden, da Schleifenvariablen normalerweise größer als oder gleich 0 sind.
Wenn wir ein size_t object verwenden, müssen wir sicherstellen, dass in allen verwendeten Kontexten, einschließlich der Arithmetik, nur nicht negative Werte gewünscht werden. Zum Beispiel würde folgendes Programm definitiv das unerwartete Ergebnis liefern:
// 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