wake-up-neo.com

size_t vs. uintptr_t

Der C-Standard garantiert, dass size_t Ein Typ ist, der jeden Array-Index enthalten kann. Dies bedeutet, dass size_t Logischerweise einen beliebigen Zeigertyp aufnehmen kann. Ich habe auf einigen Websites gelesen, dass dies legal ist und/oder immer funktionieren sollte:

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

In C99 führte der Standard die Typen intptr_t Und uintptr_t Ein, bei denen es sich um vorzeichenlose und vorzeichenlose Typen handelt, die Zeiger enthalten können:

uintptr_t p = (size_t) v;

Was ist der Unterschied zwischen der Verwendung von size_t Und uintptr_t? Beide sind nicht signiert und sollten in der Lage sein, einen beliebigen Zeigertyp aufzunehmen, sodass sie funktional identisch zu sein scheinen. Gibt es einen wirklich zwingenden Grund, uintptr_t (Oder noch besser, einen void *) Anstelle eines size_t Zu verwenden, der von der Klarheit abweicht? Gibt es in einer undurchsichtigen Struktur, in der das Feld nur von internen Funktionen behandelt wird, einen Grund, dies nicht zu tun?

Aus dem gleichen Grund ist ptrdiff_t Ein vorzeichenbehafteter Typ, der Zeigerunterschiede und daher die meisten Zeiger aufnehmen kann. Wie unterscheidet er sich von intptr_t?

Dienen nicht alle diese Typen im Grunde genommen trivial unterschiedlichen Versionen derselben Funktion? Wenn nein, warum? Was kann ich mit einem von ihnen nicht machen, was kann ich mit einem anderen nicht machen? Wenn ja, warum hat C99 der Sprache zwei im Wesentlichen überflüssige Typen hinzugefügt?

Ich bin bereit, Funktionszeiger zu ignorieren, da sie nicht für das aktuelle Problem gelten, aber ich kann sie gerne erwähnen, da ich den Verdacht habe, dass sie für die "richtige" Antwort von zentraler Bedeutung sind.

243
Chris Lutz

size_t ist ein Typ, der jeden Array-Index enthalten kann. Dies bedeutet, dass size_t logischerweise einen beliebigen Zeigertyp aufnehmen kann

Nicht unbedingt! Denken Sie beispielsweise an die Zeit der segmentierten 16-Bit-Architekturen zurück: Ein Array kann auf ein einzelnes Segment beschränkt sein (also ein 16-Bit-size_t würde tun) ABER Sie könnten mehrere Segmente haben (also ein 32-Bit intptr_t Typ würde benötigt, um das Segment sowie den Versatz darin auszuwählen). Ich weiß, dass diese Dinge in diesen Tagen von einheitlich adressierbaren, unsegmentierten Architekturen komisch klingen, aber der Standard MUSS für eine größere Vielfalt sorgen als "was 2009 normal ist", wissen Sie! -)

227
Alex Martelli

Zu Ihrer Aussage:

"Der C-Standard garantiert, dass size_t ist ein Typ, der jeden Array-Index enthalten kann. Dies bedeutet, dass logischerweise size_t sollte in der Lage sein, jeden Zeigertyp aufzunehmen. "

Dies ist eigentlich ein Irrtum (ein Missverständnis, das aus einer falschen Argumentation resultiert)(ein). Sie können denken das letztere folgt aus dem ersteren, aber das ist eigentlich nicht der Fall.

Zeiger und Array-Indizes sind nicht dasselbe. Es ist durchaus plausibel, sich eine konforme Implementierung vorzustellen, die Arrays auf 65536 Elemente beschränkt, es jedoch Zeigern ermöglicht, jeden Wert in einen massiven 128-Bit-Adressraum zu adressieren.

C99 besagt, dass die Obergrenze eines size_t Variable wird definiert durch SIZE_MAX und dies kann so niedrig wie 65535 sein (siehe C99 TR3, 7.18.3, unverändert in C11). Zeiger wären ziemlich begrenzt, wenn sie in modernen Systemen auf diesen Bereich beschränkt wären.

In der Praxis werden Sie wahrscheinlich feststellen, dass Ihre Annahme zutrifft, aber das liegt nicht daran, dass der Standard dies garantiert. Weil es tatsächlich nicht garantiert.


(ein) Dies ist übrigens nicht eine Art persönlicher Angriff, der nur angibt, warum Ihre Aussagen im Kontext des kritischen Denkens falsch sind. Die folgende Begründung ist beispielsweise ebenfalls ungültig:

Alle Welpen sind süß. Das Ding ist süß. Deshalb muss dieses Ding ein Welpe sein.

Die Niedlichkeit von Welpen spielt hier keine Rolle, ich behaupte nur, dass die beiden Tatsachen nicht zum Schluss führen, weil die ersten beiden Sätze die Existenz von niedlichen Dingen zulassen, die nicht sind Welpen.

Dies ähnelt Ihrer ersten Aussage, die nicht unbedingt die zweite vorschreibt.

86
paxdiablo

Ich werde alle anderen Antworten in Bezug auf die Argumentation mit Segmentbeschränkungen, exotischen Architekturen usw. für sich stehen lassen.

Reicht der einfache Unterschied in den Namen nicht aus, um den richtigen Typ für die richtige Sache zu verwenden?

Wenn Sie eine Größe speichern, verwenden Sie size_t. Wenn Sie einen Zeiger speichern, verwenden Sie intptr_t. Eine Person, die Ihren Code liest, wird sofort wissen, dass "aha, das ist eine Größe von etwas, wahrscheinlich in Bytes" und "oh, aus irgendeinem Grund wird hier ein Zeigerwert als Ganzzahl gespeichert".

Andernfalls können Sie einfach unsigned long (oder in diesen hier modernen Zeiten unsigned long long) für alles. Größe ist nicht alles, Typnamen haben eine Bedeutung, die nützlich ist, da sie das Programm beschreiben.

33
unwind

Möglicherweise ist die Größe des größten Arrays kleiner als ein Zeiger. Denken Sie an segmentierte Architekturen - Zeiger können 32-Bit sein, aber ein einzelnes Segment kann möglicherweise nur 64 KB adressieren (zum Beispiel die alte Real-Mode-8086-Architektur).

Während diese nicht mehr häufig in Desktop-Computern verwendet werden, soll der C-Standard auch kleine, spezialisierte Architekturen unterstützen. Es gibt noch eingebettete Systeme, die beispielsweise mit 8- oder 16-Bit-CPUs entwickelt werden.

12
Michael Burr

Ich würde mir vorstellen (und das gilt für alle Typnamen), dass es Ihre Absichten im Code besser vermittelt.

Zum Beispiel, obwohl unsigned short und wchar_t haben unter Windows die gleiche Größe (glaube ich) und verwenden wchar_t anstatt unsigned short zeigt die Absicht, dass Sie es verwenden, um ein breites Zeichen statt nur einer beliebigen Zahl zu speichern.

5
dreamlax

Ich bin mir ziemlich sicher, dass sie versuchten, alle vorhandenen Systeme einzuschließen und auch alle möglichen zukünftigen Systeme bereitzustellen, wenn sie sowohl vorwärts als auch rückwärts blickten und daran erinnerten, dass verschiedene merkwürdige Architekturen in der Landschaft verstreut waren.

So sicher, wie sich die Dinge entwickelt haben, haben wir bisher nicht so viele Typen gebraucht.

Aber selbst in LP64, einem weit verbreiteten Paradigma, benötigten wir size_t und ssize_t für die Systemaufrufschnittstelle. Man kann sich ein eingeschränkteres Legacy-System oder ein zukünftiges System vorstellen, bei dem die Verwendung eines vollständigen 64-Bit-Typs teuer ist und sie möglicherweise auf E/A-Vorgänge von mehr als 4 GB verzichten möchten, aber immer noch 64-Bit-Zeiger haben.

Ich denke, man muss sich fragen: Was könnte entwickelt worden sein, was könnte in Zukunft kommen. (Vielleicht 128-Bit-Internet-weite Zeiger für verteilte Systeme, aber nicht mehr als 64 Bit in einem Systemaufruf, oder vielleicht sogar ein "Legacy" -Limit von 32 Bit. :-) Stellen Sie sich vor, dass Legacy-Systeme möglicherweise neue C-Compiler erhalten. .

Schauen Sie sich auch an, was damals da war. Wie wäre es mit den 60-Bit-Word-/18-Bit-Pointer-Großrechnern von CDC, die neben den zig 286-Real-Mode-Speichermodellen noch weitere enthalten? Wie wäre es mit der Cray-Serie? Egal ob normales ILP64, LP64, LLP64. (Ich habe immer gedacht, dass Microsoft mit LLP64 vorgetäuscht war, es hätte P64 sein sollen.) Ich kann mir sicher vorstellen, dass ein Ausschuss versucht, alle Grundlagen abzudecken ...

3
DigitalRoss