Ich habe die C-Programmierung überprüft und es gibt nur ein paar Dinge, die mich stören.
Nehmen wir diesen Code zum Beispiel:
int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);
Ich weiß, dass ein int einen Maximalwert von positiv 2.147.483.647 halten kann. Geht man darüber hinweg, "geht es über" zur nächsten Speicheradresse, wodurch Element 2 an dieser Adresse als "-2147483648" angezeigt wird? Aber dann ist das nicht wirklich sinnvoll, da in der Ausgabe immer noch steht, dass die nächste Adresse den Wert 4, dann 5 enthält. Wenn die Zahl auf die nächste Adresse übergegangen wäre, würde das den an dieser Adresse gespeicherten Wert nicht ändern ?
Ich erinnere mich vage daran, wie ich in MIPS Assembly programmiert habe und beobachtet habe, wie sich die Werte der Adressen während des Programms schrittweise geändert haben, dass sich die diesen Adressen zugewiesenen Werte ändern würden.
Wenn ich mich nicht falsch erinnere, ist hier eine andere Frage: Wenn die einer bestimmten Adresse zugewiesene Nummer größer ist als der Typ (wie in myArray [2]), hat dies dann keinen Einfluss auf die unter der nachfolgenden Adresse gespeicherten Werte?
Beispiel: Wir haben int myNum = 4 Milliarden an der Adresse 0x10010000. Natürlich kann myNum keine 4 Milliarden speichern, daher erscheint es als negative Zahl an dieser Adresse. Obwohl diese große Zahl nicht gespeichert werden kann, hat dies keine Auswirkungen auf den unter der nachfolgenden Adresse 0x10010004 gespeicherten Wert. Richtig?
Die Speicheradressen haben gerade genug Platz, um bestimmte Zahlen- / Zeichengrößen aufzunehmen, und wenn die Größe den Grenzwert überschreitet, wird sie anders dargestellt (wie der Versuch, 4 Milliarden in das int zu speichern, aber es wird als negative Zahl angezeigt) und Daher hat dies keine Auswirkung auf die unter der nächsten Adresse gespeicherten Zahlen / Zeichen.
Entschuldigung, wenn ich über Bord gegangen bin. Ich habe den ganzen Tag einen großen Hirnfurz davon.
quelle
int c = INT.MAXINT; c+=1;
und sehen, was mit c passiert ist.Antworten:
Nein, tut es nicht. In C haben Variablen einen festen Satz von Speicheradressen, mit denen gearbeitet werden kann. Wenn Sie auf einem System mit 4 Byte arbeiten
ints
und eineint
Variable auf setzen2,147,483,647
und dann hinzufügen1
, enthält die Variable normalerweise-2147483648
. (Auf den meisten Systemen. Das Verhalten ist tatsächlich undefiniert.) Andere Speicherorte werden nicht geändert.Im Wesentlichen lässt der Compiler nicht zu, dass Sie einen Wert zuweisen, der für den Typ zu groß ist. Dies erzeugt einen Compilerfehler. Wenn Sie es mit einer Groß- / Kleinschreibung erzwingen, wird der Wert abgeschnitten.
Bitweise betrachtet, wenn der Typ nur 8 Bits speichern kann und Sie versuchen, den Wert
1010101010101
mit einer Groß- / Kleinschreibung in ihn zu zwingen , erhalten Sie die unteren 8 Bits, oder01010101
.Unabhängig davon, was Sie in Ihrem Beispiel tun
myArray[2]
,myArray[3]
wird "4" enthalten. Es gibt kein "Überlaufen". Sie versuchen, etwas zu schreiben, das mehr als 4 Bytes umfasst. Dadurch wird alles auf der oberen Ebene abgeschwächt, und die unteren 4 Bytes bleiben erhalten. Auf den meisten Systemen führt dies zu-2147483648
.Aus praktischer Sicht möchten Sie nur sicherstellen, dass dies niemals und niemals geschieht. Diese Art von Überläufen führt häufig zu schwer zu lösenden Fehlern. Mit anderen Worten, wenn Sie der Meinung sind, dass es eine Chance gibt, dass alle Ihre Werte in Milliardenhöhe liegen, verwenden Sie sie nicht
int
.quelle
Überlauf von Ganzzahlen mit Vorzeichen ist undefiniertes Verhalten. In diesem Fall ist Ihr Programm ungültig. Der Compiler muss dies nicht für Sie überprüfen, sodass möglicherweise eine ausführbare Datei erstellt wird, die anscheinend etwas Vernünftiges bewirkt. Es gibt jedoch keine Garantie dafür, dass dies der Fall ist.
Ein vorzeichenloser Ganzzahlüberlauf ist jedoch genau definiert. Es wird modulo UINT_MAX + 1 umbrechen. Der von Ihrer Variablen nicht belegte Speicher wird nicht beeinflusst.
Siehe auch https://stackoverflow.com/q/18195715/951890
quelle
int
. Ich nehme an, sie könnten Gray-Code oder BCD oder EBCDIC verwenden . Keine Ahnung, warum irgendjemand Hardware für das Rechnen mit Gray-Code oder EBCDIC entwerfen würde, aber ich weiß auch nicht, warum irgendjemandunsigned
mit Binärdaten arbeiten undint
mit etwas anderem als dem 2er-Komplement signieren würde .Hier gibt es also zwei Dinge:
Auf der Sprachebene:
In C:
Für diejenigen, die ein "was auch immer" -Beispiel wollen, habe ich gesehen:
einbiegen in:
und ja, das ist eine legitime Transformation.
Dies bedeutet, dass aufgrund einer seltsamen Compilertransformation tatsächlich das Risiko besteht, dass beim Überlauf Speicher überschrieben wird.
Hinweis: Verwenden Sie auf Clang oder gcc
-fsanitize=undefined
in Debug, um den Undefined Behavior Sanitizer zu aktivieren, der bei Unterlauf / Überlauf von Ganzzahlen mit Vorzeichen abgebrochen wird.Oder Sie können den Speicher überschreiben, indem Sie das Ergebnis der Operation verwenden, um ein Array zu indizieren (nicht markiert). Dies ist bei fehlender Unterlauf- / Überlauferkennung leider weitaus wahrscheinlicher.
Hinweis: Verwenden Sie auf Clang oder gcc
-fsanitize=address
in Debug, um den Address Sanitizer zu aktivieren, der bei einem Zugriff außerhalb der Grenzen abgebrochen wird.Auf Maschinenebene :
Es hängt wirklich von der Montageanleitung und der verwendeten CPU ab:
Add
:Beachten Sie, dass die CPU bei einem Überlauf keinen Speicher überschreibt, egal ob in Registern oder im Speicher.
quelle
Der Grund für die weitere Antwort von @ StevenBurnap liegt in der Funktionsweise von Computern auf Maschinenebene.
Ihr Array ist im Speicher abgelegt (zB im RAM). Wenn eine arithmetische Operation ausgeführt wird, wird der Wert im Speicher in die Eingangsregister der Schaltung kopiert, die die Arithmetik ausführt (die ALU: Arithmetic Logic Unit ), und die Operation wird dann an den Daten in den Eingangsregistern ausgeführt, wobei ein Ergebnis erzeugt wird im Ausgangsregister. Dieses Ergebnis wird dann an der richtigen Speicheradresse in den Speicher zurückkopiert, wobei andere Speicherbereiche unberührt bleiben.
quelle
Als Erstes (unter der Annahme des C99-Standards) möchten Sie möglicherweise den
<stdint.h>
Standardheader einbeziehen und einige der dort definierten Typen verwenden, insbesondereint32_t
eine 32-Bit-Ganzzahl mit Vorzeichen oderuint64_t
eine 64-Bit-Ganzzahl ohne Vorzeichen. Möglicherweise möchten Sie Typen wieint_fast16_t
aus Leistungsgründen verwenden.Lesen Sie die Antworten anderer, in denen erklärt wird, dass vorzeichenlose Arithmetik niemals an benachbarte Speicherstellen gelangt (oder überläuft). Vorsicht vor undefiniertem Verhalten bei signiertem Überlauf.
Wenn Sie dann genau riesige Ganzzahlen berechnen müssen (z. B. eine Fakultät von 1000 mit allen 2568 Dezimalstellen), möchten Sie Bigints, also willkürliche Präzisionszahlen (oder Bignums). Algorithmen für eine effiziente Bigint-Arithmetik sind sehr clever und erfordern normalerweise die Verwendung spezieller Maschinenbefehle (z. B. einige Add-Words mit Übertrag, falls Ihr Prozessor über diese verfügt). Daher empfehle ich in diesem Fall dringend, eine vorhandene Bigint-Bibliothek wie GMPlib zu verwenden
quelle