Не уверен, насколько этот сценарий inout безопасен, потому что массив становится недействительным по возвращении

Приведенный ниже код не дает сбоя, но я не могу объяснить почему, учитывая "ограниченные" доступные документы.

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

1 ответ

Я верю, что это то, что происходит. Когда вы назначаете

a = []

ты указываешь a в новый массив. Исходный массив все еще существует в памяти, и когда вы делаете:

b = 99

вы модифицируете исходный массив, а не новый массив, который a Рекомендации.

Какие у вас есть доказательства того, что это так?

Рассмотрите эту модификацию в своем эксперименте:

Случай 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]" 

Теперь рассмотрим это:

Случай 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]"

Очевидно, что изменение отдельных элементов a это не то же самое, что присвоение массива a,

В случае 1 мы изменили исходный массив, превратив элементы в 4, 5, а также 6и назначение b изменено a[2] как и ожидалось.

В случае 2 мы назначили [4, 5, 6] в a который не изменил исходные значения на 4, 5, а также 6 но вместо этого указал a в новый массив. Назначение b не меняется a[2] в этом случае, потому что a теперь указывает на новый массив в другом месте в памяти.

Случай 3:

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]"

В случае 3 мы можем присвоить исходный массив a1 перед назначением нового массива a, Это дает нам имя для исходного массива. когда b назначен, a1[2] модифицируется.


Из комментариев:

Ваш ответ объясняет, почему назначение b внутри функции работает. Однако, когда foo заканчивается и копирует входные переменные обратно, в этот момент я не вижу, как swift знает, как отложить освобождение исходного массива до назначения &[2].

Вероятно, это результат подсчета ссылок со стороны ARC. Исходный массив передается по ссылке на foo и счетчик ссылок увеличивается. Исходный массив не освобождается, пока счетчик ссылок не будет уменьшен в конце foo,

Это кажется таким же волосатым, как то, что документы уже запрещают - передавая одну и ту же переменную дважды как inout. Также ваш случай3 удивителен. Разве let a1 = a line не должна делать семантику struct / value и копировать снимок массива прямо сейчас?

Да. Я согласен, что Случай 3 удивителен, но он действительно раскрывает кое-что из того, что происходит под одеялом. Обычно, когда вы присваиваете один массив новой переменной, Swift не сразу делает копию. Вместо этого он просто указывает второй массив на первый и счетчик ссылок увеличивается. Это сделано ради эффективности. Копировать необходимо только при изменении одного из массивов. В этом случае, когда a модифицируется, a1 сохраняет оригинальную копию массива в памяти.

Это действительно сбивает с толку; Я не понимаю, почему a1 не получит 1,2,3. Также пусть должен быть неизменным!

Дело в том, что a1 модифицируется когда b установлен показывает, что a1 указывает на память исходного массива. Свифт, видимо, не рассматривает настройку b как изменение a1, Возможно, потому что он уже убедился a был изменчив, когда foo был вызван с &a[2],

Другие вопросы по тегам