it-swarm.com.de

Größe der Zeiger und Architektur

Wenn Sie einen Basistest durchführen, indem Sie ein einfaches C++ - Programm auf einem normalen Desktop-PC ausführen, ist es plausibel anzunehmen, dass die Größe der Zeiger eines beliebigen Typs (einschließlich Zeiger auf Funktionen) den Zielarchitekturbits entspricht.

Zum Beispiel: in 32-Bit-Architekturen -> 4 Bytes und in 64-Bit-Architekturen -> 8 Bytes.

Ich erinnere mich jedoch, dass ich das gelesen habe, es ist im Allgemeinen nicht so!

Also habe ich mich gefragt, was solche Umstände sein würden?

  • Zur Gleichheit der Größe von Zeigern auf Datentypen im Vergleich zur Größe von Zeigern auf andere Datentypen
  • Zur Gleichheit der Größe von Zeigern auf Datentypen im Vergleich zur Größe von Zeigern auf Funktionen
  • Für die Größe der Zeiger auf die Zielarchitektur
19
AKL

Nein, es ist nicht vernünftig anzunehmen. Diese Annahme kann zu Fehlern führen.

Die Größe von Zeigern (und Ganzzahltypen) in C oder C++ wird letztendlich von der C- oder C++ - Implementierung bestimmt. Normale C- oder C++ - Implementierungen werden stark von den Architekturen und den Betriebssystemen beeinflusst, auf die sie abzielen. Sie können jedoch die Größe ihrer Typen aus anderen Gründen als der Ausführungsgeschwindigkeit auswählen, z. B. aus Gründen der Unterstützung einer geringeren Speichernutzung und der Unterstützung von Code, in den nicht geschrieben wurde Sie können vollständig auf alle Schriftgrößen portiert werden oder die Verwendung großer Ganzzahlen vereinfachen.

Ich habe einen Compiler gesehen, der auf ein 64-Bit-System ausgerichtet ist, aber 32-Bit-Zeiger bereitstellt, um Programme mit geringerem Speicherbedarf zu erstellen. (Es wurde beobachtet, dass die Größe von Zeigern aufgrund der Verwendung vieler Strukturen mit vielen Verbindungen und Referenzen unter Verwendung von Zeigern ein beträchtlicher Faktor für den Speicherverbrauch war.) Quellcode, der unter der Annahme geschrieben wurde, dass die Zeigergröße dem 64-Bit-Register entspricht Größe würde brechen.

18

Es ist anzunehmen, dass im Allgemeinen die Größe von Zeigern eines beliebigen Typs (einschließlich Zeigern auf Funktionen) gleich den Zielarchitekturbits ist

Hängt davon ab. Wenn Sie eine schnelle Schätzung des Speicherverbrauchs anstreben, kann dies ausreichend sein.

(einschließlich Zeiger auf Funktionen)

Aber hier ist eine wichtige Bemerkung. Obwohl die meisten Zeiger dieselbe Größe haben, können sich die Funktionszeiger unterscheiden. Es kann nicht garantiert werden, dass ein void* Einen Funktionszeiger halten kann. Zumindest gilt dies für C. Ich weiß nichts über C++.

Also habe ich mich gefragt, was solche Umstände wären, wenn überhaupt?

Es kann unzählige Gründe geben, warum es anders ist. Wenn die Richtigkeit Ihres Programms von dieser Größe abhängt, ist es NIEMALS in Ordnung, eine solche Annahme zu treffen. Überprüfen Sie es stattdessen. Es sollte überhaupt nicht schwer sein.

Mit diesem Makro können Sie solche Dinge zur Kompilierungszeit in C überprüfen:

#include <assert.h>
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");

Beim Kompilieren wird eine Fehlermeldung ausgegeben:

$ gcc main.c 
In file included from main.c:1:
main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes"
 static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
 ^~~~~~~~~~~~~

Wenn Sie C++ verwenden, können Sie #include <assert.h> Überspringen, da static_assert Ein Schlüsselwort in C++ ist. (Und Sie können das Schlüsselwort _Static_assert In C verwenden, aber es sieht hässlich aus. Verwenden Sie stattdessen das Include und das Makro.)

Da diese beiden Zeilen so einfach in Ihren Code aufzunehmen sind, gibt es KEINE Entschuldigung, dies nicht zu tun, wenn Ihr Programm mit der falschen Zeigergröße nicht richtig funktionieren würde.

14
klutt

Es ist anzunehmen, dass im Allgemeinen die Größe von Zeigern eines beliebigen Typs (einschließlich Zeigern auf Funktionen) gleich den Zielarchitekturbits ist.

Es mag vernünftig sein, aber es ist nicht zuverlässig richtig. Ich denke also, die Antwort lautet "Nein, außer wenn Sie bereits wissen, dass die Antwort Ja lautet (und Sie sich keine Sorgen um die Portabilität machen)" .

Möglicherweise:

  • systeme können unterschiedliche Registergrößen haben und unterschiedliche zugrunde liegende Breiten für Daten und Adressierung verwenden: Es ist nicht ersichtlich, was "Zielarchitekturbits" für ein solches System überhaupt bedeuten. Daher müssen Sie einen bestimmten ABI auswählen (und wenn Sie dies getan haben, müssen Sie dies tun kenne die Antwort für diesen ABI).

  • systeme unterstützen möglicherweise verschiedene Zeigermodelle, z. B. die alten Zeiger near, far und huge. In diesem Fall müssen Sie wissen, in welchem ​​Modus Ihr Code kompiliert wird (und dann kennen Sie die Antwort für diesen Modus).

  • systeme unterstützen möglicherweise unterschiedliche Zeigergrößen, wie z. B. das bereits erwähnte X32-ABI oder eines der anderen beschriebenen beliebten 64-Bit-Datenmodelle hier

Schließlich hat diese Annahme keinen offensichtlichen Vorteil, da Sie sizeof(T) direkt für das T verwenden können, an dem Sie interessiert sind.

Wenn Sie zwischen Ganzzahlen und Zeigern konvertieren möchten, verwenden Sie intptr_t. Wenn Sie Ganzzahlen und Zeiger im selben Bereich speichern möchten, verwenden Sie einfach ein union.

9
Useless

Die "Bits" der Zielarchitektur geben Auskunft über die Registergröße. Ex. Intel 8051 ist 8-Bit und arbeitet mit 8-Bit-Registern, aber auf (externes) RAM und (externes) ROM wird mit 16-Bit-Werten zugegriffen.

8
MamCieNaHita

Für Korrektheit können Sie nichts annehmen. Sie müssen überprüfen und darauf vorbereitet sein, mit seltsamen Situationen umzugehen.

Als allgemeine Faustregel ist dies eine vernünftige Standardeinstellung Annahme .

Es ist jedoch nicht allgemein wahr. Siehe beispielsweise X32 ABI , das 32-Bit-Zeiger auf 64-Bit-Architekturen verwendet, um ein wenig Speicherplatz und Cache-Speicherplatz zu sparen. Gleiches gilt für den ILP32 ABI auf AArch64.

Um die Speichernutzung zu schätzen, können Sie Ihre Annahme verwenden, und sie ist häufig richtig.

5
Jesper Juhl

Es ist anzunehmen, dass im Allgemeinen die Größe von Zeigern eines beliebigen Typs (einschließlich Zeigern auf Funktionen) gleich den Zielarchitekturbits ist.

Wenn Sie sich alle Arten von CPUs (einschließlich Mikrocontrollern) ansehen, die derzeit hergestellt werden, würde ich nein sagen.

Extreme Gegenbeispiele wären Architekturen, bei denen zwei verschiedene Zeigergrößen im gleiches Programm verwendet werden:

x86, 16-Bit

Unter MS-DOS und 16-Bit-Windows verwendete ein "normales" Programm sowohl 16- als auch 32-Bit-Zeiger.

x86, 32-Bit segmentiert

Es gab nur wenige, weniger bekannte Betriebssysteme, die dieses Speichermodell verwendeten.

Programme verwendeten normalerweise sowohl 32- als auch 48-Bit-Zeiger.

STM8A

Diese moderne 8-Bit-Automobil-CPU verwendet 16- und 24-Bit-Zeiger. Natürlich beide im selben Programm.

AVR winzige Serie

RAM wird mit 8-Bit-Zeigern adressiert, Flash mit 16-Bit-Zeigern.

(Allerdings kann AVR tiny meines Wissens nicht mit C++ programmiert werden.)

5
Martin Rosenau

Es ist nicht korrekt, zum Beispiel können DOS-Zeiger (16 Bit) weit sein (seg + ofs).

Für die üblichen Ziele (Windows, OSX, Linux, Android, iOS) ist dies jedoch korrekt. Weil sie alle das flache Programmiermodell verwenden, das auf Paging beruht.

Theoretisch können Sie auch Systeme haben, die in x64 nur die unteren 32 Bit verwenden. Ein Beispiel ist eine ausführbare Windows-Datei, die ohne LARGEADDRESSAWARE verknüpft ist. Dies soll dem Programmierer jedoch helfen, Fehler beim Wechsel zu x64 zu vermeiden. Die Zeiger werden auf 32 Bit abgeschnitten, aber sie sind immer noch 64 Bit.

In x64-Betriebssystemen gilt diese Annahme immer, da der Flat-Modus der einzig gültige ist. Der lange Modus in der CPU erzwingt, dass GDT-Einträge 64 Bit flach sind.

Man erwähnt auch einen x32-ABI, der meiner Meinung nach auf derselben Paging-Technologie basiert und alle Zeiger dazu zwingt, den unteren 4 GB zugeordnet zu werden. Dies muss jedoch auf der gleichen Theorie wie in Windows basieren. In x64 können Sie nur den Flat-Modus verwenden.

Im 32-Bit-geschützten Modus können Zeiger bis zu 48 Bit vorhanden sein. (Segmentierter Modus). Sie können auch Callgates haben. Kein Betriebssystem verwendet diesen Modus.

4

In der Vergangenheit waren Zeiger auf Mikrocomputern und Mikrocontrollern häufig breiter als Allzweckregister, sodass die CPU genügend Speicher adressieren und dennoch in das Transistorbudget passen konnte. Die meisten 8-Bit-CPUs (wie 8080, Z80 oder 6502) hatten 16-Bit-Adressen.

Heutzutage ist eine Nichtübereinstimmung wahrscheinlicher, weil eine App nicht mehrere Gigabyte Daten benötigt. Das Einsparen von vier Byte Speicher auf jedem Zeiger ist also ein Gewinn.

Sowohl C als auch C++ bieten separate size_t, uintptr_t und off_t Typen, die die größtmögliche Objektgröße darstellen (die kleiner sein kann als die Größe eines Zeigers, wenn das Speichermodell nicht flach ist), einen integralen Typ, der breit genug ist, um einen Zeiger aufzunehmen, und einen Dateiversatz (häufig breiter als der größte Objekt im Speicher erlaubt). EIN size_t (ohne Vorzeichen) oder ptrdiff_t (signiert) ist der portabelste Weg, um die native Word-Größe zu ermitteln. Darüber hinaus garantiert POSIX, dass der System-Compiler über ein Flag verfügt, das bedeutet, dass ein long eines dieser Flags enthalten kann, dies kann jedoch nicht immer angenommen werden.

2
Davislor

Im Allgemeinen haben Zeiger die Größe 2 auf einem 16-Bit-System, 3 auf einem 24-Bit-System, 4 auf einem 32-Bit-System und 8 auf einem 64-Bit-System. Dies hängt von der Implementierung von [~ # ~] abi [~ # ~] und C ab. AMD hat lang und alt Modi, und es gibt Unterschiede zwischen AMD64 und Intel64 für Assemblersprache Programmierer, aber diese sind für höhere Sprachen verborgen.

Probleme mit C/C++ - Code sind wahrscheinlich auf schlechte Programmierpraktiken und das Ignorieren von Compiler-Warnungen zurückzuführen. Siehe: " 20 Probleme beim Portieren von C++ - Code auf die 64-Bit-Plattform ".

Siehe auch: " Können Zeiger unterschiedlich groß sein ? " und Antwort von LRiO :

... Sie fragen nach C++ und seinen kompatiblen Implementierungen, nicht nach einer bestimmten physischen Maschine. Ich müsste den gesamten Standard zitieren, um ihn zu beweisen , aber die einfache Tatsache ist, dass er keine Garantie für das Ergebnis von sizeof (T) gibt *) für jedes T und (als Folge) keine Garantie dafür, dass sizeof (T1 *) == sizeof (T2 *) für jedes T1 und T2).

Hinweis: Wobei von JeremyP beantwortet wird , C99 Abschnitt 6.3.2.3, Unterabschnitt 8:

Ein Zeiger auf eine Funktion eines Typs kann in einen Zeiger auf eine Funktion eines anderen Typs konvertiert werden und wieder zurück; Das Ergebnis muss mit dem ursprünglichen Zeiger verglichen werden. Wenn ein konvertierter Zeiger zum Aufrufen einer Funktion verwendet wird, deren Typ nicht mit dem Typ kompatibel ist, auf den verwiesen wird, ist das Verhalten undefiniert.

In GCC können Sie falsche Annahmen vermeiden, indem Sie integrierte Funktionen verwenden: " Überprüfung der integrierten Funktionen der Objektgröße ":

Integrierte Funktion: size_t __builtin_object_size (const void * ptr, int type)

ist ein integriertes Konstrukt, das eine konstante Anzahl von Bytes von ptr bis zum Ende des Objekts zurückgibt, auf das der ptr-Zeiger zeigt (falls dies zur Kompilierungszeit bekannt ist). Um die Größe dynamisch zugeordneter Objekte zu bestimmen, stützt sich die Funktion auf die Zuweisungsfunktionen, die aufgerufen werden, um den Speicher zu erhalten, der mit dem Attribut alloc_size deklariert werden soll (siehe Allgemeine Funktionsattribute). __builtin_object_size wertet seine Argumente niemals auf Nebenwirkungen aus. Wenn sie Nebenwirkungen enthalten, wird (size_t) -1 für Typ 0 oder 1 und (size_t) 0 für Typ 2 oder 3 zurückgegeben. Wenn mehrere Objekte vorhanden sind, kann ptr darauf verweisen, und alle sind zur Kompilierungszeit bekannt Die zurückgegebene Zahl ist das Maximum der verbleibenden Byteanzahl in diesen Objekten, wenn Typ & 2 0 ist, und das Minimum, wenn sie ungleich Null ist. Wenn es nicht möglich ist zu bestimmen, auf welche Objekte ptr zur Kompilierungszeit zeigt, sollte __builtin_object_size (size_t) -1 für Typ 0 oder 1 und (size_t) 0 für Typ 2 oder 3 zurückgeben.

0
Rob