2015-11-13 12 views
5

Код ниже не падает, но я не могу объяснить, почему, учитывая доступные «доступные» документы.Не уверен, как этот сценарий бездействия безопасен, потому что массив становится недействительным по возврату

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

это интересно. вы пробовали его с разными уровнями оптимизации? Параметры INOUT сначала копируются, а затем обрабатываются и, наконец, копируются обратно. как копия реализована, кто знает? – user3441734

+0

Да, я скомпилировал с и без -O, тот же результат. быстрый 2.1. – KarlP

+0

У меня такие же результаты. может быть, идея заключается не в том, чтобы копировать результат, если по умолчанию «вне привязки» ... – user3441734

ответ

2

Я считаю, что это то, что происходит. При назначении

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


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

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

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

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

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

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

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

+0

В вашем ответе объясняется, почему работает функция b внутри функции. Однако, когда foo завершает и копирует inout-переменные обратно, в этот момент я не вижу, как быстро известно, что отсрочить освобождение исходного массива до присвоения '& [2]'. Кажется столь же волосатым, как те, что уже запрещены документами - передача одной и той же переменной в два раза больше, чем inout. Также ваш случай3 удивителен. Должна ли строка 'let a1 = a' выполнять семантику struct/value и копировать моментальный снимок массива прямо? Это действительно сбивает с толку; Я не понимаю, почему a1 не получит '1,2,3'. Также 'let' должен быть неизменным! – KarlP

+0

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

+0

Тот факт, что 'a1' изменяется при установке' b', показывает, что 'a1' указывает на память исходного массива. Swift, по-видимому, не рассматривает установку 'b' как модификацию' a1'. Возможно, потому, что он уже убедился, что 'a' изменен, когда' foo' был вызван с '& a [2]'. – vacawama