2014-09-04 5 views
6

Jemand schrieb das folgende C-Programm und fragte, warum gcc erlaubt "die Basisadresse eines Arrays zu ändern". Er war sich bewusst, dass der Code schrecklich ist, wollte es aber trotzdem wissen. Ich fand die Frage interessant genug, weil die Beziehung zwischen Arrays und Zeigern in C subtil ist (siehe die Verwendung des Adressoperators auf dem Array! "Warum sollte das irgendjemand tun?"), Verwirrend und folglich oft missverstanden. Die Frage wurde gestrichen, aber ich dachte, ich frage sie erneut, mit dem richtigen Kontext und - wie ich hoffe - einer richtigen Antwort darauf. Hier ist das ursprüngliche Prog.Können wir die Basisadresse eines Arrays durch einen Zeiger auf ein Array mit Brute-Force ändern?

static char* abc = "kj"; 

void fn(char**s) 
{ 
    *s = abc; 
} 

int main() 
{ 
    char str[256]; 
    fn(&str); 
} 

Es kompiliert mit gcc (mit Warnungen), Links und Läufe. was geschieht hier? Können wir die Basisadresse eines Arrays ändern, indem wir seine Adresse nehmen und sie in einen Zeiger auf einen Zeiger umwandeln (nachdem alle Arrays fast Zeiger in C sind, nicht wahr?) Und ihm zuweisen?

+0

für die Nachwelt, [hier] (http://stackoverflow.com/questions/25660493/assigning-to-a-char-array-using-assignment-operator-and-double-pointers) ist die gelöschte Frage –

Antwort

4

Das Programm ändert nicht die "Basisadresse" des Arrays. Es versucht nicht einmal.

Was Sie an Fn übergeben, ist die Adresse eines Stücks von 256 Zeichen im Speicher. Es ist numerisch identisch mit dem Zeiger, der str würde in anderen Ausdrücken verfallen, nur anders getippt. Hier bleibt das Array wirklich ein Array - das Anwenden des Adressoperators auf ein Array ist eine der Instanzen, bei denen ein Array nicht zu einem Zeiger wird. Das Inkrementieren von &str zum Beispiel würde es numerisch um 256 erhöhen. Dies ist wichtig für mehrdimensionale Arrays, die, wie wir wissen, tatsächlich eindimensionale Arrays von Arrays in C sind. Das Inkrementieren des ersten Index eines "zweidimensionalen" Arrays muss die Adresse an den Anfang des nächsten "Chunks" oder "Zeile" weiterleiten.

Jetzt der Haken. In Bezug auf fn verweist die Adresse, die Sie übergeben, auf einen Ort, der eine andere Adresse enthält. Das ist nicht wahr; Es zeigt auf eine Sequenz von Zeichen. Beim Ausdruck dieser als Zeiger interpretierten Bytefolge werden die Bytewerte 'A' 65 oder 0x41 angezeigt.

Fn, jedoch denken, dass der Speicher zeigte auf eine Adresse enthält, überschreibt es mit der Adresse, wo "kj" im Speicher residiert. Da in str genügend Speicher reserviert ist, um eine Adresse zu halten, ist die Zuweisung erfolgreich und führt zu einer verwendbaren Adresse an diesem Ort.

Es sollte beachtet werden, dass dies natürlich nicht garantiert funktioniert. Die häufigste Ursache für einen Fehler sollten Ausrichtungsprobleme sein - str ist, denke ich, nicht erforderlich, um für einen Zeigerwert richtig ausgerichtet zu sein. Der Standard schreibt vor, dass Argumente für Funktionen mit den Parameterdeklarationen zuweisungskompatibel sein müssen. Beliebige Zeigertypen können nicht einander zugewiesen werden (man muss dafür Void-Pointer durchlaufen oder casten).

Bearbeiten: david.pfx wies darauf hin, dass (auch mit einer richtigen Besetzung) der Code ruft undefined Verhalten. Der Standard erfordert den Zugriff auf Objekte über kompatible lvalues ​​(einschließlich referenzierter Zeiger) in Abschnitt 6.5/7 des letzten öffentlichen Entwurfs. Beim richtigen Casting und Kompilieren mit gcc -fstrict-aliasing -Wstrict-aliasing=2 ... warnt gcc vor dem "typ punning". Der Grund dafür ist, dass der Compiler frei davon ausgehen kann, dass inkompatible Zeiger den gleichen Speicherbereich nicht modifizieren; hier muss nicht angenommen werden, dass fn den Inhalt von str ändert. Dies ermöglicht dem Compiler, Wegladungen (z. B. von Speicher zu Register), die ansonsten notwendig wären, zu optimieren. Dies wird eine Rolle bei der Optimierung spielen; ein wahrscheinliches Beispiel, bei dem eine Debugsitzung den Fehler nicht reproduzieren könnte (wenn das zu debuggende Programm ohne Optimierung für Debugzwecke kompiliert würde). Davon abgesehen würde ich mich wundern, wenn ein nicht optimierender Compiler hier unerwartete Ergebnisse erzeugen würde, also lasse ich den Rest der Antwort unverändert stehen.-

Ich habe eine Reihe von Debug-Printfs eingefügt, um zu veranschaulichen, was vor sich geht. Ein Live-Beispiel kann hier gesehen werden: http://ideone.com/aL407L.

#include<stdio.h> 
#include<string.h> 
static char* abc = "kj"; 

// Helper function to print the first bytes a char pointer points to 
void printBytes(const char *const caption, const char *const ptr) 
{ 
    int i=0; 
    printf("%s: {", caption); 
    for(i=0; i<sizeof(char *)-1; ++i) 
    { 
     printf("0x%x,", ptr[i]); 
    } 
    printf("0x%x ...}\n", ptr[sizeof(char *)-1]); 
} 

// What exactly does this function do? 
void fn(char**s) { 
    printf("Inside fn: Argument value is %p\n", s); 
    printBytes("Inside fn: Bytes at address above are", (char *)s); 

    // This throws. *s is not a valid address. 
    // printf("contents: ->%s<-\n", *s); 

    *s = abc; 
    printf("Inside fn: Bytes at address above after assignment\n"); 
    printBytes("   (should be address of \"kj\")", (char *)s); 

    // Now *s holds a valid address (that of "kj"). 
    printf("Inside fn: Printing *s as string (should be kj): ->%s<-\n", *s); 

} 


int main() { 
    char str[256]; 

    printf("size of ptr: %zu\n", sizeof(void *)); 
    strcpy(str, "AAAAAAAA"); // 9 defined bytes 

    printf("addr of \"kj\": %p\n", abc); 
    printf("str addr: %p (%p)\n", &str, str); 
    printBytes("str contents before fn", str); 

    printf("------------------------------\n"); 
    // Paramter type does not match! Illegal code 
    // (6.5.16.1 of the latest public draft; incompatible 
    // types for assignment). 
    fn(&str); 

    printf("------------------------------\n"); 

    printBytes("str contents after fn (i.e. abc -- note byte order!): ", str); 
    printf("str addr after fn -- still the same! --: %p (%p)\n", &str, str); 

    return 0; 
} 
+2

I wundern Sie sich, wie Sie in der Zeit waren, mit einem so langen Beitrag zu antworten, als die Frage vor 6 Minuten gestellt wurde und Ihre Antwort auch vor 6 Minuten erschien? !!! Die Eingabe nur des Programmcodes dauert mehr als 1 Minute. –

+1

Nun, Ctrl-V braucht 1/100. ;-). Und ich habe hier festgestellt, dass SO, wenn ich eine Frage stelle, mir die Möglichkeit gibt, die Antwort zusammen mit der Frage zu schreiben. Dieses Selbst-Q/A-Muster ist eines der unterstützten Formate für SO, damit die Leute ihre Einsichten teilen können (Sie können keine Antwort ohne Frage schreiben, denke ich). Dies ist das erste Mal, dass ich es ausprobiert habe. –

+0

@VladfromMoskau Dies wurde von einer anderen Frage kopiert, die jetzt gelöscht wurde –

5

Es kann nicht (auch theoretisch) arbeiten, weil Arrays keine Zeiger sind:


  • int arr[10]:

    • Eingesetzte Menge des Speichers ist sizeof(int)*10 Bytes

    • Die Werte von arr und &arr sind notwendigerweise identisch

    • arr auf eine gültige Speicheradresse, aber kann nicht an eine andere Speicheradresse zeigen


    eingestellt werden
  • int* ptr = malloc(sizeof(int)*10):

    • Menge des verwendeten Speichers ist sizeof(int*) + sizeof(int)*10 Bytes

    • Die Werte von ptr und &ptr nicht notwendigerweise identisch sind (in der Tat sind sie meist unterschiedlich)

    • ptr kann so eingestellt werden, um beide Punkt gültige und ungültige Speicheradressen, so oft wie Sie werden

+0

Nun, das O-OP (nicht ich) war verwirrt durch die Tatsache, dass du die Adresse des Arrays nehmen, es dereferenzieren und zuordnen kannst (nachdem er es effektiv gegossen hat). Er erwartete, dass dies den "Wert" wie bei anderen Zeigern verändern würde, wie in '(* (& x)) = 1'. Da dieser "Wert" das Array war, dachte er, dass er das Array irgendwie (nicht seinen Inhalt) ändert. In meiner Antwort sezierte ich die Wirkung eines auf ein Array angewendeten Adressoperators, die Interpretation des resultierenden Argumentwertes innerhalb von 'fn' nach der Typumwandlung und die Auswirkungen auf die beteiligten Werte. –

+0

@PeterSchneider: Du hast mich hier verwirrt. Ich habe dir +1 gegeben, weil ich die Frage interessant fand. Dann habe ich gemerkt, dass du auch derjenige warst, der darauf geantwortet hat (vom ersten Kommentar bis zur Antwort), was ein bisschen komisch ist, muss ich sagen, aber das ist in Ordnung, soweit es mich betrifft ... Jedenfalls jetzt Sie sagen "O-OP (nicht ich) war verwirrt". Was bedeutet "O-OP" und warum bezeichnen Sie sich selbst als "nicht ich" ??? –

+0

Ich habe das Format "answer your use question" verwendet, um Weisheit zu verbreiten ;-). Es ist nicht ungewöhnlich; SO bietet tatsächlich eine Option für die Bearbeitung Ihrer Antwort zusammen mit der Frage, die mir bis heute nicht bekannt war. Das macht die OP-eigene Antwort zuerst angezeigt. - Wie ich in der Frage erläutert habe, basiert es auf einem, der von einem anderen Benutzer gefragt und dann gelöscht wurde. Diese Person wäre das "Original Original Poster" oder O-OP gewesen. Ich war es nicht. Ich bin der OP in diesem Thread. –

2

Was Sie hier haben, ist einfach Undefined Behavior.

Der Parameter für die Funktion wird als Zeiger auf Zeiger auf Zeichen deklariert. Das übergebene Argument ist Pointer-to-Array-of-256-char. Der Standard lässt Konvertierungen zwischen einem Zeiger und einem anderen zu, aber da das Objekt, auf das gezeigt wird, kein Zeiger auf ein Zeichen ist, wird der Zeiger auf Undefiniertes Verhalten dereferenziert.

n1570 S6.5.3.2/4:

Wenn ein ungültiger Wert auf den Zeiger zugeordnet ist, das Verhalten des unären Operator * ist undefiniert.

Es ist sinnlos zu spekulieren, wie sich Undefined Behavior auf verschiedenen Implementierungen auswirkt. Es ist einfach falsch.


einfach klar zu sein, ist die UB in dieser Zeile:

*s=abc; 

Der Zeiger s an ein Objekt des richtigen Typs zeigt nicht (char*), so dass die Verwendung von * ist UB .

+0

Ich glaube nicht, dass es UB ist (naja, gcc lässt uns den falschen Zeigertyp übergeben, der streng gesprochen einen Cast erfordert, aber nehmen wir einen Cast an, wenn wir 'fn()' aufrufen). Der übergebene Wert ist nicht ungültig. Es ist ein gültiger Zeiger auf das Array. Dereferenzierung ist gut definiert, wenn die Ausrichtungsanforderungen erfüllt sind. Cf n1570, 6.3.2.3/7: "Ein Zeiger auf einen Objekttyp kann [explizit, P. S.] in einen Zeiger auf einen anderen Objekttyp umgewandelt werden." Der Absatz behandelt dann Ausrichtungsprobleme und das Verhalten beim Konvertieren in char *, beschränkt jedoch nicht den Ziel-Zeigertyp. –

+0

Beachten Sie auch, dass der Code niemals "* s" (z. B. "s") dereferenziert, was illegal wäre und auf Systemen mit Speicherschutz abstürzen würde, zumindest bevor "abc" zu "* s" zugewiesen wurde. –

+1

@PeterSchneider: '* s = abc' ist UB laut Standard. Es spielt keine Rolle, ob Sie es "Dereferenzierung" nennen oder nicht (ich tue). Siehe Bearbeiten. –

Verwandte Themen