2014-01-21 15 views
6

Dies ist der Code, den ich nicht verstehe, es einfach eine Zeichenfolge umkehren.C Zeiger Arithmetik

#include <stdio.h> 

void strrev(char *p) 
{ 
    char *q = p; 
    while(q && *q) ++q; 
    for(--q; p < q; ++p, --q) 
    *p = *p^*q, 
    *q = *p^*q, 
    *p = *p^*q; 
} 

int main(int argc, char **argv) 
{ 
    do { 
    printf("%s ", argv[argc-1]); strrev(argv[argc-1]); 
    printf("%s\n", argv[argc-1]); 
    } while(--argc); 

    return 0; 
} 

Das einzige Stück Code, das ich nicht verstehe, ist diese: while(q && *q) ++q;, es verwendet wird, um die eos zu finden. Ist das nicht dasselbe wie while(*q) ++q;, da q niemals 0 wird? Wie kann der Autor des Codes sicher sein, dass q oder *q 0 werden?

Dieser Code stammt aus dieser Frage: How do you reverse a string in place in C or C++?

+3

Es schützt die Funktion vor dem Zurückstellen eines NULL-Pointerarguments. –

+1

Beachten Sie, dass dieses Programm nicht der Spezifikation entspricht, da der "Namensraum" 'str *' für Funktionen reserviert ist. – unwind

+13

Dieser Code ist einfach entsetzlich. Es wäre viel besser, wenn du es wegwirfst. –

Antwort

28

David Heffernans Kommentar ist korrekt. Dieser Code ist erschreckend.

Der Punkt des Codes, über den Sie nachfragen, ist das Überspringen der Dereferenzierung q, wenn es null ist. Daher glaubt der Autor des Codes, dass q null sein könnte. Unter welchen Umständen kann q null sein? Das offensichtlichste ist: wenn null ist.

Also mal sehen, was der Code macht, wenn null ist.

Also das erste, was wir tun, ist q dekrementieren, was null ist.Wahrscheinlich wird dies umwickeln und wir werden als Ergebnis erhalten q enthält die größten möglichen Zeiger.

Da null kleiner als alles außer null ist und q nicht mehr null ist, ist dies der Fall. Wir betreten die Schleife ...

++p, --q) 
    *p = *p^*q, 

Und sofort Dereferenzierung Null.

*q = *p^*q, 
    *p = *p^*q; 
} 

übrigens bei Coverity verweisen wir auf diese als „zukunfts Null-Fehler“ - das ist das Muster, in dem ein Codepfad gibt an, dass ein Wert möglicherweise null erwartet wird, und dann später auf dem gleichen Der Codepfad geht davon aus, dass es nicht null ist. Es ist sehr häufig.

OK, also ist dieser Code vollständig gebrochen, wenn er als Argument null ist. Gibt es andere Möglichkeiten, dass es kaputt ist? Was passiert, wenn wir eine leere Zeichenfolge eingeben?

void strrev(char *p) // Assumption: *p is 0 
{ 
    char *q = p; // *q is 0 
    while(q && *q) ++q; // The second condition is not met so the body is skipped. 
    for(--q; // q now points *before valid memory*. 
     p < q // And we compare an invalid pointer to a valid one. 

Haben wir eine Garantie in C haben, die besagt, dass, wenn Sie aus einem Zeiger auf gültige Speicher subtrahieren, und dann diesen Zeiger auf einen anderen Zeiger vergleichen zu können, dass der Vergleich sinnvoll ist? Weil mir das als unglaublich gefährlich erscheint. Ich kenne den C-Standard nicht gut genug, um zu sagen, ob dies ein undefiniertes Verhalten ist oder nicht.

Darüber hinaus verwendet dieser Code den schrecklichen "zwei Zeichen mit xor" Trick tauschen. Warum in aller Welt würde das jemand tun? Es erzeugt größeren, langsameren Maschinencode und ist schwerer zu lesen, zu verstehen und zu warten. Wenn Sie zwei Dinge austauschen möchten, tauschen Sie sie.

Es verwendet auch den Komma-Operator, um mehrere Anweisungen in einer einzigen Anweisung zu setzen, um den Horror von Klammern um den Körper der for zu vermeiden. Was ist der Zweck dieser Kuriosität? Der Zweck des Codes ist nicht zu zeigen, wie viele Betreiber Sie kennen, ist es in erster Linie zu mit dem Leser des Codes kommunizieren.

Die Funktion ändert auch ihren formalen Parameter, was das Debuggen erschwert.

+2

+1 Hölle eines Code-Review. _appalling_ scheint ein Kompliment, nachdem ich das gelesen habe :) –

+4

Was C garantiert über den Vergleich von Zeigern ist, dass Sie es auf sinnvolle Weise tun können, solange beide Zeiger auf dasselbe Objekt sind, oder Zeiger auf Mitglieder desselben Aggregatobjekts . Als Sonderfall werden Zeiger auf ein Element nach dem letzten Element eines Arrays zu Vergleichszwecken als Teil des Arrays betrachtet, dürfen aber niemals wirklich dereferenziert werden. Ein Zeiger auf eins vor dem Anfang eines Arrays ist Undefined Behavior. –

+3

Die genauen Details von C zum Addieren, Subtrahieren und Vergleichen von Zeigern finden Sie unter N1570, §6.5.6 Zusätzliche Operatoren, Absatz 8 auf der PDF-Seite 111 (als Seite 93 bezeichnet), §6.5.8 Relationale Operatoren, Absätze 4 und 5 auf den PDF-Seiten 113/114 (gekennzeichnet als Seiten 95/96) und §6.5.9 Gleichbehandlungsstellen, Absätze 5 und 6. –

1

Ist es nicht das gleiche wie while(*q) ++q;, da q nie 0 sein?

while(q && *q) wird verwendet, um sicherzustellen, dass q nicht NULL und *q ist nicht NUL. Die Verwendung von while(*q) ist auch zulässig, wenn q nicht auf NULL zeigt.

void string_rev(char *p) 
{ 
    char *q = p; 

    if(q == NULL) 
     return; 

    while(*q) ++q; 
    for(--q; p < q; ++p, --q) 
     *p = *p^*q, 
     *q = *p^*q, 
     *p = *p^*q; 

} 

Wie kann der Autor des Codes sicher sein, dass q oder *q0 sein werden?

while(q && *q), q könnte ein NULL Zeiger, wenn p zeigt auf den NULL und Schleife endet, bevor der Schleifenkörper eintritt. Ansonsten kann während der Operation kein Zeiger NULL sein. Jetzt hängt der Schleifenabschluss von *q ab. Sobald q das Ende der Zeichenfolge erreicht, wird *q zu 0 und die Schleife wird beendet.

5

Der Code

while(q && *q) 

ist eine Abkürzung für

while(q != NULL && *q != '\0') 

so testen Sie, wenn q (am Anfang gleich p) nicht NULL ist. Das bedeutet, dass die mit NULL-Argument aufgerufene Funktion in dieser while-Schleife nicht abstürzt. (Aber es wird immer noch in der zweiten Schleife abstürzen).

+1

OP fragt, ob er 'while (* q)' verwenden kann oder nicht. – haccks

+2

Nein, die Frage war, ob while (q && * q) und while (* q) gleich sind. –

+0

OK. Beantwortete dies die Frage mit dem Satz 'while (q && * q) ist eine Kurzschrift für while (q! = NULL && * q! = '\ 0')'? – haccks