2015-11-13 2 views
5

Der untenstehende Code stürzt nicht ab, aber ich kann nicht begründen, warum die "begrenzten" Dokumente zur Verfügung stehen.Nicht sicher, wie dieses Inout-Szenario sicher ist, da das Array bei der Rückgabe ungültig wird

func foo(inout a: [Int], inout b: Int) { 
    a = [] 
    b = 99 
} 

var arr = [1,2,3] 

// confusion: where does the "out" of b go to? 
// storage for a was already cleared/invalidated 
foo(&arr, b: &arr[2]) 

print(arr) // arr is empty 
+0

das ist interessant. Hast du es mit verschiedenen Optimierungsstufen versucht? INOUT-Parameter werden zuerst kopiert, dann verarbeitet und schließlich zurückkopiert. Wie ist die Kopie implementiert, wer weiß? – user3441734

+0

ja ich kompiliert mit und ohne -O, dasselbe Ergebnis. schnell 2.1. – KarlP

+0

Ich habe die gleichen Ergebnisse. vielleicht ist die Idee, das Ergebnis nicht zu kopieren, wenn 'out of bound' standardmäßig ist ... – user3441734

Antwort

2

Ich glaube, das ist was passiert. Wenn Sie

a = [] 

zuordnen weisen Sie a auf ein neues Array. Das ursprüngliche Array existiert noch im Speicher und wenn Sie das tun:

b = 99 

Sie den Original-Array ändern, nicht das neue Array, dass a Referenzen.

Welche Beweise haben Sie, dass dies der Fall ist?

Betrachten Sie diese Änderungen an Ihrem Experiment:

Fall 1:

func foo(inout a: [Int], inout b: Int) { 
    a[0] = 4 
    a[1] = 5 
    a[2] = 6 
    b = 99 
} 

var arr = [1,2,3] 

foo(&arr, b: &arr[2]) 

print(arr) // prints "[4, 5, 99]" 

Betrachten wir nun diese:

Fall 2:

func foo(inout a: [Int], inout b: Int) { 
    a = [4, 5, 6] 
    b = 99 
} 

var arr = [1,2,3] 

foo(&arr, b: &arr[2]) 

print(arr) // prints "[4, 5, 6]" 

Natürlich ist das Ändern der einzelnen Elemente von a nicht das Gleiche wie das Zuordnen eines Arrays zu a.

In Fall 1, modifizierten wir die ursprüngliche Anordnung der Elemente in 4, 5 und 6, und die Zuordnung der b Drehen verändert a[2] wie erwartet.

Im Fall 2 zugewiesen wir [4, 5, 6]-a, die nicht die ursprünglichen Werte 4 geändert haben, 5 und 6 wies aber stattdessen a in ein neues Array. Die Zuweisung von b ändert sich nicht a[2] in diesem Fall, da a jetzt auf ein neues Array an einer anderen Speicherposition verweist. 3

Fall:

func foo(inout a: [Int], inout b: Int) { 
    let a1 = a 
    a = [4, 5, 6] 
    b = 99 
    print(a) // prints "[4, 5, 6]" 
    print(a1) // prints "[1, 2, 99]" 
} 

var arr = [1,2,3] 

foo(&arr, b: &arr[2]) 

print(arr) // prints "[4, 5, 6]" 

In Fall 3 sind wir in der Lage, die ursprüngliche Array a1 bevor die Zuweisung eines neuen Arrays zu a zuzuweisen. Dies gibt uns einen Namen für das ursprüngliche Array. Wenn b zugewiesen ist, wird a1[2] geändert.


Aus den Kommentaren:

Ihre Antwort erklärt, warum die Zuordnung innerhalb der Funktion Werke b.Wenn foo jedoch endet und die Inout-Variablen zurückkopiert, sehe ich unter diesen Punkt nicht, wie schnell es möglich ist, die Freigabe des Originalarrays bis nach der & [2] Zuweisung aufzuschieben.

Es ist wahrscheinlich, dass dies ein Ergebnis der Referenzzählung durch ARC ist. Das ursprüngliche Array wird mit Bezug auf foo übergeben und der Referenzzähler wird inkrementiert. Das ursprüngliche Array wird nicht freigegeben, bis der Referenzzähler am Ende von foo dekrementiert ist.

Es scheint genauso haarig wie was die Docs bereits nicht zulassen - die gleiche Variable zweimal als Inout übergeben. Auch Ihr Fall3 ist überraschend. Sollte nicht die let a1 = eine Zeile Struktur/Wert Semantik und kopieren Sie einen Snapshot von das Array richtig dann?

Ja. Ich stimme zu, dass Fall 3 überraschend ist, aber es zeigt einiges von dem, was unter den Deckeln geschieht. Wenn Sie einer neuen Variablen ein Array zuweisen, erstellt Swift normalerweise nicht sofort eine Kopie. Stattdessen wird nur das zweite Array auf den ersten und den Referenzzähler inkrementiert. Dies geschieht aus Effizienzgründen. Es ist nur notwendig, eine Kopie zu erstellen, wenn eines der Arrays geändert wird. Wenn in diesem Fall a geändert wird, behält a1 die ursprüngliche Kopie des Arrays im Speicher.

Das ist wirklich verwirrend; Ich verstehe nicht, warum a1 nicht 1,2,3 bekommen würde. Auch eine lassen Sie unveränderlich sein!

Die Tatsache, dass a1 modifiziert wird, wenn b gesetzt ist, zeigt, dass a1 auf den Speicher des ursprünglichen Arrays zeigt. Swift scheint die Einstellung b als Modifizierung a1 anscheinend nicht in Betracht zu ziehen. Vielleicht weil es bereits dafür gesorgt hat, dass a änderbar war, als foo mit &a[2] aufgerufen wurde.

+0

Ihre Antwort erklärt, warum die Zuweisung zu b innerhalb der Funktion funktioniert. Wenn foo jedoch endet und die Inout-Variablen zurückkopiert, sehe ich an diesem Punkt nicht, wie schnell es möglich ist, die Zuweisung des ursprünglichen Arrays bis nach der & 2-Zuweisung aufzuheben. Es scheint genauso haarig zu sein wie das, was die Docs bereits ablehnen - indem sie die gleiche Variable zweimal wie inout weitergeben. Auch Ihre case3 ist überraschend. Sollte die 'let a1 = a'-Zeile keine Semantik struct/value sein und dann einen Snapshot des Arrays kopieren? Das ist wirklich verwirrend; Ich verstehe nicht, warum a1 nicht "1,2,3" bekommen würde. Auch ein "Let" sollte unveränderlich sein! – KarlP

+0

Ja. Ich stimme zu, dass Fall 3 überraschend ist, aber es zeigt einiges von dem, was unter den Deckeln geschieht. Wenn Sie einer neuen Variablen ein Array zuweisen, erstellt Swift normalerweise nicht sofort eine Kopie. Stattdessen wird nur das zweite Array auf den ersten und den Referenzzähler inkrementiert. Dies geschieht aus Effizienzgründen. Es ist nur notwendig, eine Kopie zu erstellen, wenn eines der Arrays geändert wird. In diesem Fall, wenn "a" modifiziert wird, behält "a1" die ursprüngliche Kopie des Arrays im Speicher. – vacawama

+0

Die Tatsache, dass 'a1' modifiziert wird, wenn' b' gesetzt ist, zeigt, dass 'a1' auf den Speicher des ursprünglichen Arrays zeigt. Swift sieht anscheinend nicht vor, 'b' so einzustellen, dass' a1' verändert wird. Vielleicht, weil es bereits dafür gesorgt hat, dass "a" änderbar war, wenn 'foo' mit' & a [2] 'aufgerufen wurde. – vacawama