Zeiger auf eins vor dem ersten Element des Arrays

8

In C wird gesagt, dass, wenn Zeiger auf dasselbe Array oder ein Element nach dem Ende dieses Arrays verweisen, die Arithmetik und Vergleiche gut definiert sind. Was ist dann mit einem vor dem ersten Element des Arrays? Ist es in Ordnung, solange ich es nicht dereferenziere?

Gegeben

int a[10], *p;
p = a;

(1) Ist das Schreiben legal --p?

(2) Ist es legal, p-1in einem Ausdruck zu schreiben ?

(3) Wenn (2) in Ordnung ist, kann ich das behaupten p-1 < a?

Hierfür gibt es einige praktische Bedenken. Stellen Sie sich eine reverse()Funktion vor, die einen C-String umkehrt, der mit endet '\0'.

#include <stdio.h>

void reverse(char *p)
{
    char *b, t;

    b = p;
    while (*p != '\0')
        p++;
    if (p == b)      /* Do I really need */
        return;      /* these two lines? */
    for (p--; b < p; b++, p--)
        t = *b, *b = *p, *p = t;
}

int main(void)
{
    char a[] = "Hello";

    reverse(a);
    printf("%s\n", a);
    return 0;
}

Muss ich den Code wirklich einchecken?

Bitte teilen Sie Ihre Ideen aus sprachrechtlicher / praktischer Sicht mit und wie Sie mit solchen Situationen umgehen würden.

aafulei
quelle
2
(1) Es mag legal sein, es zu schreiben, aber das Ergebnis ist undefiniertes Verhalten, wenn Sie es ausführen. Bei segmentierten Architekturen (Intel 80286, 80386 usw.) kann das Ergebnis völlig verwirrend sein. (2) Ditto. (3) N / A, aber die Antwort lautet nein. Für Ihre Hardware und Ihre Betriebssysteme sind Sie möglicherweise sicher, aber der C-Standard garantiert dies nicht.
Jonathan Leffler
1
Beantwortet das deine Frage? Was sind all die gängigen undefinierten Verhaltensweisen, die ein C ++ - Programmierer kennen sollte? Suchen Sie im Abschnitt nach Hinweisen.
Jesaja

Antworten:

6

(1) Ist es legal --p zu schreiben?

Es ist "legal", wie es die C-Syntax zulässt, aber es ruft undefiniertes Verhalten auf. Für den Zweck, den relevanten Abschnitt in der Norm zu finden, --pist gleichbedeutend mit p = p - 1(außer pwird nur einmal bewertet). Dann:

C17 6.5.6 / 8

Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert.

Die Bewertung ruft undefiniertes Verhalten auf, dh es spielt keine Rolle, ob Sie den Zeiger de-referenzieren oder nicht - Sie haben bereits undefiniertes Verhalten aufgerufen.

Außerdem:

C17 6.5.6 / 9:

Wenn zwei Zeiger subtrahiert werden, zeigen beide auf Elemente desselben Array-Objekts oder einen nach dem letzten Element des Array-Objekts.

Wenn Ihr Code ein "soll" in der ISO-Norm verletzt, ruft er undefiniertes Verhalten auf.

(2) Ist es legal, p-1 in einen Ausdruck zu schreiben?

Gleich wie (1), undefiniertes Verhalten.


Beispiele dafür, wie dies in der Praxis zu Problemen führen kann: Stellen Sie sich vor, das Array befindet sich ganz am Anfang einer gültigen Speicherseite. Wenn Sie außerhalb dieser Seite dekrementieren, liegt möglicherweise eine Hardware-Ausnahme oder eine Zeiger-Trap-Darstellung vor. Dies ist kein völlig unwahrscheinliches Szenario für Mikrocontroller, insbesondere wenn sie segmentierte Speicherzuordnungen verwenden.

Lundin
quelle
Könnten Sie bitte einige Kommentare zum Beispielcode in der Frage abgeben? Ist die Prüfung notwendig / richtig / ausreichend?
aafulei
Es ist möglich, dass *p == '\0'am Anfang. Diese Prüfung soll verhindern, dass p--in der for-Schleife.
aafulei
@aafulei Ja, das habe ich gemerkt. Sie benötigen die zusätzliche Prüfung wegen der schlecht geschriebenen Schleife. Die richtige Lösung besteht darin, die Schleife neu zu schreiben, Beispiel: godbolt.org/z/R4TuwT
Lundin
Vielen Dank für Ihren Code. Es ist klug. Ich würde vorschlagen, dass Sie es in Ihre Antwort aufnehmen, damit mehr Leute es sehen können. Die einzige Sache ist, dass, wenn es eine ungerade Anzahl von Zeichen in der Zeichenfolge gibt (ohne zu zählen '\0'), am Ende ein Selbsttausch (Austausch mit sich selbst) stattfindet. Aber das ist gut so. Bitte nehmen Sie sich auch etwas länger Zeit für eine Kreuzvalidierung, bevor ich das Häkchen setzen kann.
aafulei
Wenn p-1 in einem Ausdruck ungültig ist, p=p-1wäre ungültig. Und p--ist p=p-1. Würden Sie argumentieren, dass das Dekrementieren eines Zeigers ungültig ist?
Harper
-2

Die Verwendung dieser Art von Zeigerarithmetik ist eine schlechte Codierungspraxis, da dies zu einer erheblichen Anzahl schwer zu debuggender Probleme führen kann.

Ich musste so etwas nur einmal in mehr als 20 Jahren benutzen. Ich habe eine Rückruffunktion geschrieben, aber ich hatte keinen Zugriff auf die richtigen Daten. Die aufrufende Funktion lieferte einen Zeiger im Inneren einem richtigen Array , und ich brauchte das Byte kurz vor diesem Zeiger.

Da ich Zugriff auf den gesamten Quellcode hatte und das Verhalten mehrmals überprüfte, um zu beweisen, dass ich das bekomme, was ich brauche, und es von anderen Kollegen überprüfen ließ, entschied ich, dass es in Ordnung ist, es in die Produktion zu lassen.

Die richtige Lösung wäre gewesen, die Aufruferfunktion zu ändern, um den richtigen Zeiger zurückzugeben, aber das war zeitlich und finanziell nicht machbar (dieser Teil der Software wurde von einem Drittanbieter lizenziert).

Es a[-1]ist also möglich, sollte aber NUR mit sehr guter Sorgfalt in ganz bestimmten Situationen verwendet werden. Ansonsten gibt es keinen guten Grund, diese Art von selbstverletzendem Voodoo jemals zu machen.


Hinweis: Bei einer ordnungsgemäßen Analyse ist in meinem Beispiel offensichtlich, dass ich nicht vor dem Beginn eines ordnungsgemäßen Arrays auf ein Element zugegriffen habe, sondern auf das Element vor einem Zeiger, der sich garantiert innerhalb desselben Arrays befand.


Bezugnehmend auf den bereitgestellten Code:

  • es ist NICHT OK, p [-1] mit zu verwenden reverse(a);;
  • Es ist in Ordnung (-ish), es zu verwenden reverse(a+1);, da Sie innerhalb des Arrays bleiben.
Virolino
quelle