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-1
in 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.
c
language-lawyer
pointer-arithmetic
aafulei
quelle
quelle
Antworten:
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,
--p
ist gleichbedeutend mitp = p - 1
(außerp
wird nur einmal bewertet). Dann: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 Ihr Code ein "soll" in der ISO-Norm verletzt, ruft er undefiniertes Verhalten auf.
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.
quelle
*p == '\0'
am Anfang. Diese Prüfung soll verhindern, dassp--
in der for-Schleife.'\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.p-1
in einem Ausdruck ungültig ist,p=p-1
wäre ungültig. Undp--
istp=p-1
. Würden Sie argumentieren, dass das Dekrementieren eines Zeigers ungültig ist?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:
reverse(a);
;reverse(a+1);
, da Sie innerhalb des Arrays bleiben.quelle