size_t vs. uintptr_t

Lesezeit: 11 Minuten

Benutzeravatar von Chris Lutz
Chris Lutz

Das garantiert die C-Norm size_t ist ein Typ, der jeden Array-Index enthalten kann. Das bedeutet logischerweise, size_t sollte jeden Zeigertyp aufnehmen können. Ich habe auf einigen Websites gelesen, die ich bei Google gefunden habe, dass dies legal ist und / oder immer funktionieren sollte:

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

Also wurde dann in C99 der Standard eingeführt intptr_t und uintptr_t Typen, die signierte und unsignierte Typen sind, die garantiert Zeiger enthalten können:

uintptr_t p = (size_t) v;

Was ist also der Unterschied zwischen der Verwendung von size_t und uintptr_t? Beide sind nicht signiert und beide sollten in der Lage sein, jeden Zeigertyp aufzunehmen, sodass sie funktional identisch erscheinen. Gibt es einen wirklich zwingenden Grund für die Verwendung? uintptr_t (oder besser noch a void *) eher als ein size_t, außer Klarheit? 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 Grunde, ptrdiff_t war ein vorzeichenbehafteter Typ, der in der Lage ist, Zeigerunterschiede zu halten, und daher in der Lage, fast jeden Zeiger zu halten, also wie unterscheidet er sich von intptr_t?

Bieten nicht all diese Typen im Grunde trivialerweise unterschiedliche Versionen derselben Funktion? Wenn nicht, warum? Was kann ich mit einem von ihnen nicht tun, was ich mit einem anderen nicht tun kann? Wenn ja, warum hat C99 der Sprache zwei im Wesentlichen überflüssige Typen hinzugefügt?

Ich bin bereit, Funktionszeiger zu ignorieren, da sie nicht auf das aktuelle Problem zutreffen, aber Sie können sie gerne erwähnen, da ich den leisen Verdacht habe, dass sie für die “richtige” Antwort von zentraler Bedeutung sein werden.

Benutzeravatar von Alex Martelli
Alex Martell

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

Nicht unbedingt! Erinnern Sie sich zum Beispiel an die Tage segmentierter 16-Bit-Architekturen: Ein Array kann auf ein einzelnes Segment beschränkt sein (also ein 16-Bit size_t tun würde), ABER Sie könnten mehrere Segmente haben (also eine 32-Bit intptr_t Typ wäre erforderlich, um das Segment sowie den darin enthaltenen Offset auszuwählen). Ich weiß, dass diese Dinge in diesen Tagen von einheitlich adressierbaren unsegmentierten Architekturen seltsam klingen, aber der Standard MUSS für eine größere Vielfalt sorgen als “was 2009 normal ist”, wissen Sie!-)

  • Dies erklärt zusammen mit den zahlreichen anderen, die zu demselben Schluss gekommen sind, den Unterschied zwischen size_t und uintptr_t aber was ist mit ptrdiff_t und intptr_t – Wären nicht beide in der Lage, auf fast jeder Plattform denselben Wertebereich zu speichern? Warum haben sowohl vorzeichenbehaftete als auch vorzeichenlose Integer-Typen in Zeigergröße, insbesondere wenn ptrdiff_t erfüllt bereits den Zweck eines vorzeichenbehafteten ganzzahligen Typs in Zeigergröße.

    – Chris Lutz

    23. September 2009 um 17:15 Uhr

  • Stichwort dort ist „on fast jede Plattform”, @Chris. Eine Implementierung kann Zeiger auf den Bereich 0xf000-0xffff beschränken – dies erfordert ein 16-Bit-intptr_t, aber nur ein 12/13-Bit-ptrdiff_t.

    – paxdiablo

    23. September 2009 um 20:59 Uhr

  • @Chris, nur für Hinweise innerhalb des gleichen Arrays ist es wohldefiniert, ihre Differenz zu nehmen. Auf genau denselben segmentierten 16-Bit-Architekturen (Array muss sich in einem einzelnen Segment befinden, aber zwei verschiedene Arrays können sich in verschiedenen Segmenten befinden) müssen Zeiger 4 Bytes sein, aber Zeiger Unterschiede könnten 2 Bytes sein!

    – Alex Martelli

    24. September 2009 um 1:44 Uhr

  • @AlexMartelli: Außer dass Zeigerunterschiede positiv oder negativ sein können. Die Norm verlangt size_t mindestens 16 Bit sein, aber ptrdiff_t mindestens 17 Bit sein (was in der Praxis bedeutet, dass es wahrscheinlich mindestens 32 Bit sein wird).

    – Keith Thompson

    27. August 2013 um 23:35 Uhr

  • Vergiss segmentierte Architekturen, was ist mit einer modernen Architektur wie x86-64? Frühe Implementierungen dieser Architektur bieten Ihnen nur einen adressierbaren 48-Bit-Bereich, aber die Zeiger selbst sind ein 64-Bit-Datentyp. Der größte zusammenhängende Speicherblock, den Sie vernünftigerweise adressieren könnten, wäre 48-Bit, also muss ich mir das vorstellen SIZE_MAX sollte nicht 2**64 sein. Dies verwendet wohlgemerkt eine flache Adressierung; es ist keine Segmentierung erforderlich, um eine Fehlanpassung dazwischen zu haben SIZE_MAX und der Bereich eines Datenzeigers.

    – Andon M. Coleman

    2. November 2013 um 22:36 Uhr

Benutzeravatar von paxdiablo
paxdiablo

Zu deiner Aussage:

„Das garantiert die C-Norm size_t ist ein Typ, der jeden Array-Index enthalten kann. Das bedeutet logischerweise, size_t sollte jeden Zeigertyp aufnehmen können.”

Dies ist eigentlich ein Trugschluss (ein Missverständnis, das sich aus einer falschen Argumentation ergibt).(a). Du könntest denken Letzteres folgt aus Ersterem, aber das ist nicht wirklich der Fall.

Zeiger und Array-Indizes sind nicht das gleiche. Es ist durchaus plausibel, sich eine konforme Implementierung vorzustellen, die Arrays auf 65536 Elemente begrenzt, Zeigern jedoch erlaubt, jeden Wert in einen massiven 128-Bit-Adressraum zu adressieren.

C99 besagt, dass die Obergrenze von a size_t Variable wird definiert durch SIZE_MAX und das 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 eigentlich nicht garantiere es.


(a) Das ist nicht Übrigens eine Art persönlicher Angriff, der nur erklärt, warum Ihre Aussagen im Kontext des kritischen Denkens falsch sind. Ungültig ist beispielsweise auch die folgende Begründung:

Alle Welpen sind süß. Das Ding ist süß. Also muss das Ding ein Welpe sein.

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

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

  • Anstatt zu wiederholen, was ich in den Kommentaren für Alex Martelli gesagt habe, bedanke ich mich einfach für die Klarstellung, wiederhole aber die zweite Hälfte meiner Frage (die ptrdiff_t vs. intptr_t Teil).

    – Chris Lutz

    23. September 2009 um 17:21 Uhr

  • @Ivan, wie bei der meisten Kommunikation muss es ein gemeinsames Verständnis bestimmter grundlegender Elemente geben. Wenn Sie diese Antwort als “Spaß machen” ansehen, versichere ich Ihnen, dass dies ein Missverständnis meiner Absicht ist. Angenommen, Sie beziehen sich auf meinen Kommentar zum „logischen Fehlschluss“ (ich kann keine andere Möglichkeit erkennen), war dies als sachliche Aussage gemeint, nicht als eine Aussage, die auf Kosten des OP gemacht wurde. Wenn Sie einige vorschlagen möchten Beton Verbesserung, um die Möglichkeit von Missverständnissen zu minimieren (und nicht nur eine allgemeine Beschwerde), würde ich gerne in Betracht ziehen.

    – paxdiablo

    8. Oktober 2018 um 21:10 Uhr


  • @Ivan, war mit den von Ihnen vorgeschlagenen Änderungen nicht wirklich zufrieden, habe ein Rollback durchgeführt und auch versucht, unbeabsichtigte Beleidigungen zu entfernen. Wenn Sie weitere Änderungen anzubieten haben, würde ich vorschlagen, einen Chat zu starten, damit wir darüber diskutieren können.

    – paxdiablo

    9. Oktober 2018 um 5:08 Uhr

  • @paxdiablo okay, ich denke, “das ist eigentlich ein Trugschluss” ist weniger bevormundend.

    – ivan_pozdeev

    9. Oktober 2018 um 5:37 Uhr

Benutzeravatar von unwind
entspannen

Ich lasse alle anderen Antworten bezüglich der Argumentation mit Segmentbeschränkungen, exotischen Architekturen usw. für sich stehen.

Ist das nicht einfach Unterschied in den Namen Grund genug, den richtigen Typ für das Richtige 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, hier ist ein Zeigerwert, der aus irgendeinem Grund als Ganzzahl gespeichert wird”.

Ansonsten könntest du einfach verwenden unsigned long (oder, in diesen hier modernen Zeiten, unsigned long long) für alles. Größe ist nicht alles, Typnamen haben Bedeutung, was nützlich ist, da es hilft, das Programm zu beschreiben.

  • Ich stimme zu, aber ich habe über einen Hack/Trick nachgedacht (den ich natürlich klar dokumentieren würde), bei dem es darum geht, einen Zeigertyp in a zu speichern size_t aufstellen.

    – Chris Lutz

    23. September 2009 um 17:19 Uhr

  • @MarkAdler Standard erfordert nicht, dass Zeiger insgesamt als Ganzzahlen darstellbar sind: Jeder Zeigertyp kann in einen Integer-Typ konvertiert werden. Außer wie zuvor angegeben, ist das Ergebnis implementierungsdefiniert. Wenn das Ergebnis nicht im Integer-Typ dargestellt werden kann, ist das Verhalten undefiniert. Das Ergebnis muss nicht im Wertebereich eines ganzzahligen Typs liegen. Also nur void*, intptr_t und uintptr_t garantiert jeden Zeiger auf Daten darstellen können.

    – Andrew Svietlichnyy

    30. Juni 2018 um 15:47 Uhr

  • Das ist zu naives Denken. Wenn Sie zB generische Struct-Felder ausrichten müssen, könnte size_t vs pointers falsch sein. Sie müssen dann uintptr_t verwenden, da nur dies die gleiche Ausrichtung und den gleichen Offset garantiert.

    – ländlich

    19. Februar 2021 um 9:24 Uhr

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

Während diese in Desktop-Rechnern nicht mehr häufig verwendet werden, soll der C-Standard sogar kleine, spezialisierte Architekturen unterstützen. Es werden zum Beispiel immer noch eingebettete Systeme mit 8- oder 16-Bit-CPUs entwickelt.

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

Obwohl zum Beispiel unsigned short und wchar_t sind die gleiche Größe unter Windows (glaube ich), mit wchar_t Anstatt von unsigned short zeigt die Absicht, dass Sie es verwenden werden, um ein breites Zeichen zu speichern, und nicht nur eine willkürliche Zahl.

  • Aber hier gibt es einen Unterschied – auf meinem System wchar_t ist viel größer als ein unsigned short Daher wäre es falsch, das eine für das andere zu verwenden und ein ernsthaftes (und modernes) Portabilitätsproblem zu schaffen, während die Portabilität zwischen ihnen Bedenken hat size_t und uintptr_t scheinen in den fernen Ländern von 1980 zu liegen (zufälliger Stich im Dunkeln auf das Datum, dort)

    – Chris Lutz

    23. September 2009 um 6:09 Uhr

  • Touché! Aber dann wieder, size_t und uintptr_t haben immer noch implizite Verwendungen in ihren Namen.

    – Traumlax

    23. September 2009 um 6:17 Uhr

  • Sie tun es, und ich wollte wissen, ob es dafür eine Motivation gibt, die über die reine Klarheit hinausgeht. Und es stellt sich heraus, dass es das gibt.

    – Chris Lutz

    23. September 2009 um 17:16 Uhr

Wenn ich sowohl nach hinten als auch nach vorne schaue und mich daran erinnere, dass verschiedene seltsame Architekturen über die Landschaft verstreut waren, bin ich mir ziemlich sicher, dass sie versuchten, alle bestehenden Systeme zu verpacken und auch für alle möglichen zukünftigen Systeme zu sorgen.

So sicher, wie sich die Dinge ergeben haben, haben wir bisher nicht so viele Typen benötigt.

Aber selbst in LP64, einem ziemlich verbreiteten Paradigma, brauchten wir size_t und ssize_t für die Systemaufrufschnittstelle. Man kann sich ein eingeschränkteres älteres oder zukünftiges System vorstellen, bei dem die Verwendung eines vollständigen 64-Bit-Typs teuer ist und sie möglicherweise auf E/A-Operationen mit mehr als 4 GB setzen 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 der Zukunft kommen. (Vielleicht 128-Bit-Zeiger für verteilte Systeme im Internet, aber nicht mehr als 64 Bit in einem Systemaufruf oder vielleicht sogar eine “alte” 32-Bit-Grenze. 🙂 Bild, dass alte Systeme neue C-Compiler bekommen könnten. .

Schauen Sie sich auch an, was es damals gab. Abgesehen von den zig 286 Real-Mode-Speichermodellen, was ist mit den CDC 60-Bit-Wort/18-Bit-Zeiger-Mainframes? Wie wäre es mit der Cray-Serie? Egal normales ILP64, LP64, LLP64. (Ich dachte immer, Microsoft wäre mit LLP64 anmaßend, es hätte P64 sein sollen.) Ich kann mir durchaus vorstellen, dass ein Komitee versucht, alle Grundlagen abzudecken …

  • Aber hier gibt es einen Unterschied – auf meinem System wchar_t ist viel größer als ein unsigned short Daher wäre es falsch, das eine für das andere zu verwenden und ein ernsthaftes (und modernes) Portabilitätsproblem zu schaffen, während die Portabilität zwischen ihnen Bedenken hat size_t und uintptr_t scheinen in den fernen Ländern von 1980 zu liegen (zufälliger Stich im Dunkeln auf das Datum, dort)

    – Chris Lutz

    23. September 2009 um 6:09 Uhr

  • Touché! Aber dann wieder, size_t und uintptr_t haben immer noch implizite Verwendungen in ihren Namen.

    – Traumlax

    23. September 2009 um 6:17 Uhr

  • Sie tun es, und ich wollte wissen, ob es dafür eine Motivation gibt, die über die reine Klarheit hinausgeht. Und es stellt sich heraus, dass es das gibt.

    – Chris Lutz

    23. September 2009 um 17:16 Uhr

size_t vs. uintptr_t

Neben anderen guten Antworten:

size_t ist darin definiert <stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <uchar.h>, <wchar.h>. Es ist mindestens 16-Bit.

uintptr_t ist darin definiert <stdint.h>. es ist Optional. Eine konforme Bibliothek definiert es möglicherweise nicht, wahrscheinlich weil es keinen ausreichend breiten Integer-Typ gibt, um a umzuwandeln void*uintptr_tvoid *.

Beide sind vorzeichenlose Ganzzahl Typen.

Beachten Sie das Optional Begleiter intptr_t ist ein vorzeichenbehaftete Ganzzahl Typ.

1426370cookie-checksize_t vs. uintptr_t

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy