webentwicklung-frage-antwort-db.com.de

Gibt C-Standards an, wie weit sich der Übertrag beim Inkrementieren eines Zeigers ausbreitet?

Betrachten Sie die folgenden Situationen:

  • Der National Semiconductor SC/MP hat Zeiger, die, wenn Sie sie fortlaufend erhöhen, von 0x0FFF auf 0x0000 rollen, weil die Inkrementierungsschaltung den Übertrag nicht über das untere Byte des höheren Bytes hinaus verbreitet. Wenn ich zum Beispiel while(*ptr++) ausführen möchte, um eine mit Null endende Zeichenfolge zu durchlaufen, kann ich ptr beenden, wenn er außerhalb des Arrays zeigt.

  • Auf dem PDP-10 , weil ein Computer-Word länger als eine Adresse ist1Ein Zeiger kann Tags und andere Daten in der oberen Hälfte des Words enthalten, die die Adresse enthalten. Wenn in dieser Situation ein Zeiger inkrementiert wird, kann dies dazu führen, dass andere Daten geändert werden. Das Gleiche gilt für sehr frühe Macintoshs, bevor die ROMs 32-Bit sauber waren.

Meine Frage ist also, ob der C-Standard sagt, was ein Zeigerinkrement wirklich bedeutet. Soweit ich das beurteilen kann, geht der C-Standard davon aus, dass er bitweise genauso arbeiten sollte wie das Inkrementieren einer Ganzzahl. Das gilt aber nicht immer, wie wir gesehen haben.

Kann ein standardkonformer C-Compiler einen einfachen adda a0, 1 ausgeben2 einen Zeiger inkrementieren, ohne zu prüfen, ob das Vorhandensein oder das Fehlen einer Übertragsausbreitung nicht zu einer Verrücktheit führt?


1: Beim PDP-10 ist eine Adresse 18 Bit breit, aber ein Maschinenwort ist 36 Bit breit. Ein Maschinenwort kann entweder zwei Zeiger (praktisch für LISP) oder einen Zeiger enthalten, plus Bitfelder, die beispielsweise "Hinzufügen einer weiteren Indirektionsebene", Segmente, Offsets usw. bedeuten. Oder ein Maschinenwort kann natürlich keine Zeiger enthalten, aber das ist es nicht relevant für diese Frage.

2: Fügen Sie einer Adresse eine hinzu. Das ist 68000 Assembler.

10
Wilson

Die Leute fragen oft: "Warum hat C überhaupt undefiniertes Verhalten?". Und das ist ein großartiges Beispiel für einen der großen Gründe dafür.

Bleiben wir beim NS SC/MP-Beispiel. Wenn die Hardware vorschreibt, dass das Erhöhen des Zeigerwerts 0x0FFF nicht ganz richtig funktioniert, haben wir zwei Möglichkeiten:

  1. Übersetzen Sie den Code p++ in das Äquivalent von if(p == 0x0FFF) p = 0x1000; else p++;.

  2. Übersetzen Sie p++ in ein gradliniges Inkrement, aber ordnen Sie die Dinge so an, dass kein ordnungsgemäß zugewiesenes Objekt eine Adresse mit 0x0FFF überlappt, sodass, wenn jemand Code schreibt, der schließlich den Zeigerwert 0x0FFF ändert und eine hinzukommt und eine bizarre Antwort erhält Sie können sagen "das ist undefiniert, also kann alles passieren".

Bei Annäherung an # 1 ist der generierte Code größer und langsamer. Wenn Sie sich Ansatz 2 zuwenden, ist der generierte Code maximal effizient. Und wenn sich jemand über das bizarre Verhalten beschwert, fragt er, warum der Compiler keinen Code hätte ausgeben können, der etwas "Vernünftigeres" getan hat, können Sie einfach sagen: "Unser Mandat war, so effizient wie möglich zu sein."

7
Steve Summit

Das Verhalten der Zeigerarithmetik wird vom C-Standard nur so lange festgelegt, wie das Ergebnis auf ein gültiges Objekt oder auf ein gültiges Objekt zeigt. Darüber hinaus sagt der Standard nicht, wie die Bits eines Zeigers aussehen. Eine Implementierung kann sie an ihre eigenen Zwecke anpassen.

Nein, der Standard sagt also nicht aus, was passiert, wenn ein Zeiger so weit inkrementiert wird, dass die Adresse überrollt wird.

Wenn die while-Schleife, auf die Sie sich beziehen, nur ein Element über das Ende des Arrays hinausgeht, ist sie in C sicher. (Laut Standard ist ptr auf ein Element über das Ende des Arrays hinaus erhöht worden, und x zeigt auf ein beliebiges Element Im Array, einschließlich des ersten, muss x < ptr wahr sein. Wenn ptr intern gewälzt wurde, ist die C-Implementierung dafür verantwortlich, dass der Vergleich weiterhin funktioniert.)

Wenn Ihre while-Schleife ptr um mehr als ein Element über das Ende des Arrays hinaus inkrementieren kann, definiert der C-Standard das Verhalten nicht.

14

Eine beträchtliche Anzahl von Plattformen verfügt über Adressierungsmethoden, die nicht einfach über bestimmte Grenzen hinweg indexieren können. Der C-Standard ermöglicht Implementierungen zwei allgemeine Ansätze für die Handhabung (die möglicherweise zusammen verwendet werden, aber in der Regel nicht verwendet werden):

  1. Verzichten Sie darauf, dass die Funktionen des Compilers, des Linkers oder der malloc- Objekte Objekte so anordnen, dass sie problematische Grenzen überschreiten.

  2. Führen Sie Adressberechnungen auf eine Weise durch, die über beliebige Grenzen hinweg indiziert werden kann, selbst wenn dies weniger effizient ist als der Code für die Adressberechnung, der dies nicht kann.

In den meisten Fällen führt Ansatz Nr. 1 zu Code, der schneller und kompakter ist, der Code kann jedoch in seiner Fähigkeit, Speicher effektiv zu nutzen, eingeschränkt sein. Wenn der Code zum Beispiel viele Objekte mit jeweils 33.000 Byte benötigt, wäre eine Maschine mit 4 MB Heap-Speicher, unterteilt in "starre" 64-KB-Blöcke, auf die Erzeugung von 64 Objekten (einen für jeden Block) beschränkt, obwohl Platz vorhanden sein sollte 127 von ihnen. Ansatz Nr. 2 führt häufig zu viel langsamerem Code, jedoch kann dieser Code möglicherweise den Heap-Speicherplatz effektiver nutzen.

Interessanterweise würden 16-Bit- oder 32-Bit-Ausrichtungsanforderungen dazu führen, dass viele 8-Bit-Prozessoren effizienteren Code generieren können als willkürliche Ausrichtungen (da sie beim Indizieren zwischen den Bytes eines Words eine Seitenüberquerungslogik unterlassen könnten) Nie zuvor gesehen haben 8-Bit-Compiler die Möglichkeit, solche Alignments auch auf Plattformen durchzusetzen und zu nutzen, auf denen dies erhebliche Vorteile bieten kann.

1
supercat

Der C-Standard weiß nichts über die Implementierung und der Standard kümmert sich nicht um die Implementierung. Es sagt nur, was die Wirkung der Zeigerarithmetik ist.

C erlaubt etwas, das als undefiniertes Verhalten bezeichnet wird. C ist es egal, ob das Ergebnis der Zeigerarithmetik einen Sinn hat (dh es liegt nicht außerhalb der Grenzen oder der durch die Implementierung definierte Speicher wurde nicht umbrochen). Wenn es passiert, ist es die UB. Es ist Aufgabe des Programmierers, UB zu verhindern, und C hat keine Standardmechanismen zum Erkennen oder Verhindern von UB.

0
P__J__