Какая разница, часть Третья: fixed и fixed
Вчера получил письмо, которое начиналось так:
У меня есть вопрос про буфера фиксированного размера в C #:
unsafe struct FixedBuffer { public fixed int buffer[100]; }
Поскольку я объявил buffer как fixed , его нельзя перемещать…
И моё сердце ёкнуло. Это один из тех глубоко несчастливых случаев, когда тонкие решения, принятые в мелочах проектирования языка, поощряют недопонимание.
При выполнении в небезопасном коде арифметики указателей над управляемым объектом, вам нужно гарантировать, что сборщик мусора не переместит память, на которую вы смотрите. Если сборка мусора случится в другом потоке в тот момент, когда вы выполняете арифметику указателей над объектом, указатели могут съехать. Поэтому, C# классифицирует все переменные как либо «зафиксированные», либо как «перемещаемые». Если вы хотите заниматься арифметикой указателей над перемещаемым объектом, вы можете использовать ключевое слово «fixed», чтобы сказать «эта локальная переменная содержит данные, которые сборщик мусора двигать не должен». При сборке мусора, сборщику нужно посмотреть на все локальные переменные всех выполняемых вызов (потому, конечно же, что все объекты в этих локальных переменных должны оставаться живыми); если он видит локальную переменную, помеченную «fixed», то он отмечает себе запрет перемещать адресованную ею память, даже если это приведёт к фрагментации управляемой кучи. (Вот почему важно держать объекты зафиксированными как можно менее долго.) Так что обычно мы используем «fixed» в смысле «зафиксирован на месте».
Но это не то, что означает «fixed» в данном контексте; тут оно означает «обсуждаемый буфер имеет фиксированный размер в одну сотню целых» - в некотором смысле, это эквивалентно генерации сотни целых полей в этой структуре.
Очевидно, мы часто используем одно ключевое слово для обозначения концептуально одинаковых вещей. Например, в C# мы многими способами применяем ключевое слово «internal», но все они концептуально одинаковы. Оно используется только для обозначения «доступ к некоторой сущности разрешён всему коду этой сборки».
Иногда мы используем одно и то же ключевое слово для обозначения совершенно разных вещей, и полагаемся на контекст при выяснении пользователем того, какой смысл подразумевался. Например:
var results = from c in customers where c.City == "London" select c;
и
class C<T> where T : IComparable<T>
Должно быть, понятно, что «where» используеся двумя совершенно разными способами: для построения фильтра в запросе, и для объявления ограничения на параметр обобщённого типа.
Мы доставляем людям неприятности, когда одно ключевое слово используется двумя разными способами, но разница невелика, как в нашем примере выше. Письмо пользователя продолжилось целой кучей вопросов, основанных на некорректном предположении, что расположение в памяти буфера фиксированного размера автоматически зафиксировано.
Ну, тут можно сказать, что это всего лишь неудачное совпадение терминов; что «фиксированного размера» и «фиксированного расположения» просто случайно используют слово «fixed» в разных смыслах, вот ведь незадача.
Но их связь еще глубже: нельзя безопасно обращаться к данным, хранимым в буфере фиксированного размера, если контейнер этого буфера не был зафиксирован в памяти. Эти две концепции на самом деле достаточно тесно связаны, но не совсем одно и то же.
С одной стороны, гораздо понятнее было бы использование двух ключевых слов, скажем «pinned» и «fixed». Но, с другой стороноы, оба использования «fixed» разрешены только в небезопасном коде. Ключевое предположение насчёт всех возможностей небезопасного кода – в том, что если вы готовы писать небезопасный код на C#, то вы уже программист-эксперт, который полностью понимает управление памятью в CLR. Вот почему мы заставляем вас помечать код «unsafe»; это указывает, что вы отключаете систему безопасности и знаете, что делаете.
Заметная доля ключевых слов C# применяются двумя или более способами: fixed, into, partial, out, in, new, delegate, where, using, class, struct, true, false, base, this, event, return и void – все имеют как минимум два различных значения. Большинство из них понятны из контекста, но, по крайней мере, первые три – fixed, into и partial – вызвали достаточно недоумений, чтобы я получил вопросы про их различия от озадаченных пользователей. Следующими я рассмотрю «into» и «partial».