Fixed c что это
Кроме указателей на простые типы можно использовать указатели на структуры. А для доступа к полям структуры, на которую указывает указатель, используется операция ->:
Обращаясь к указателю p->X = 30; мы можем получить или установить значение свойства структуры, на которую указывает указатель. Обратите внимание, что просто написать p.X=30 мы не можем, так как p — это не структура Point, а указатель на структуру.
Альтернативой служит операция разыменования: (*p).X = 30;
Стоит отметить, что указатель может указывать только на те структуры, которые не имеют полей ссылочных типов (в том числе полей, которые генерируются компилятором автоматически для автосвойств).
Указатели на массивы и stackalloc
С помощью ключевого слова stackalloc можно выделить память под массив в стеке. Смысл выделения памяти в стеке в повышении быстродействия кода. Посмотрим на примере вычисления квадратов чисел:
Оператор stackalloc принимает после себя массив, на который будет указывать указатель. int* square = stackalloc int[size]; .
Для манипуляций с массивом создаем указатель p: int* p = square; , который указывает на первый элемент массива, в котором всего 7 элементов. То есть с помощью указателя p мы сможем перемещаться по массиву square.
Далее в цикле происходит подсчет квадратов чисел от 1 до 7. В цикле для установки значения (квадрата числа — i * i) по адресу, который хранит указатель, выполняется выражение:
Затем происходит инкремент указателя p++ , и указатель p смещается вперед на следующий элемент в массиве square.
Чуть более сложный пример — вычисление факториала:
Также с помощью оператора stackalloc выделяется память для 7 элементов массива. И также для манипуляций с массивом создаем указатель p: int* p = factorial; , который указывает на первый элемент массива, в котором всего 7 элементов
Далее начинаются уже сами операции с указателем и подсчет факториала. Так как факториал 1 равен 1, то присваиваем первому элементу, на который указывает указатель p, единицу с помощью операции разыменования: *(p++)= 1;
Для установки некоторого значения по адресу указателя применяется выражение: *p=1 . Но кроме этого тут происходит также инкремент указателя p++ . То есть сначала первому элементу массива присваивается единица, потом указатель p смещается и начинает указывать уже на второй элемент. Мы могли бы написать это так:
Чтобы получить предыдущий элемент и сместиться назад, можно использовать операцию декремента: Console.WriteLine(*(—p)); . Обратите внимание, что операции *(—p) и *(p—) различаются, так как в первом случае сначала идет смещение указателя, а затем его разыменовывание. А во втором случае — наоборот.
Затем вычисляем факториал всех остальных шести чисел: *p = p[-1] *i; . Обращение к указателям как к массивам представляет альтернативу операции разыменовывания для получения значения. В данном случае мы получаем значение предыдущего элемента.
И в заключении, используя указатель factorial, выводим факториалы всех семи чисел.
Оператор fixed и закрепление указателей
Ранее мы посмотрели, как создавать указатели на типы значений, например, int или структуры. Однако кроме структур в C# есть еще и классы, которые в отличие от типов значений, помещают все связанные значения в куче. И в работу данных классов может в любой момент вмешаться сборщик мусора, периодически очищающий кучу. Чтобы фиксировать на все время работы указатели на объекты классов используется оператор fixed .
Допустим, у нас есть класс Point:
Зафиксируем указатель с помощью оператора fixed:
Оператор fixed создает блок, в котором фиксируется указатель на поле объекта person. После завершения блока fixed закрепление с переменных снимается, и они могут быть подвержены сборке мусора.
Кроме адреса переменной можно также инициализировать указатель, используя массив, строку или буфер фиксированного размера:
При инициализации указателей на строку следует учитывать, что указатель должен иметь тип char* .
Fixed c что это
Язык C# также поддерживает указатели (pointer), однако несколько ограниченно. Ограниченность заключается в том, что применение указателей не поощряют, поскольку справедливо считается, что это может повлиять на надежность как кода, так и среды выполнения в целом.
Указатель — это всего лишь обычная переменная, содержащая в себе адрес памяти, в которой содержится переменная какого-либо типа (адрес переменной). Другое ограничение C# — указатели могут быть объявлены только для удержания адреса переменной и массива. В отличие от ссылочных типов (reference types), типы указателей (pointer types) не отслеживаются механизмом сбора мусора по умолчанию (default garbage collection). По той же самой причине указателям не разрешено указывать на ссылочный тип (reference type) или даже на тип структуры, которая содержит в себе ссылочный тип. Можно сказать, что указатели могут указывать только на неуправляемые типы (unmanaged types), которые включают в себя все базовые типы данных (basic data types), типы перечисления (enum types), другие типы указателей и структуры, которые содержат только неуправляемые типы.
[Объявление указателя]
Основная форма объявления переменной указателя следующая:
Здесь звездочка * обозначает оператор разыменования (de-reference operator). Например, строка
объявляет переменную указателя ptr, которая может держать в себе адрес переменной типа int. Обратный оператор генерирования ссылки (reference operator, &) может использоваться для получения адреса переменной. Например, у нас есть переменная
Оператор &x даст нам адрес переменной x, который мы можем присвоить переменной указателя.
Здесь мы рассмотрели обычное простое использование указателей, применяемое на языках C и C++. На языке C# все происходит похожим образом, однако есть некоторые отличия.
[Небезопасный код (Unsafe Code)]
Правилами языка C# определено, что операторы могут выполняться либо в безопасном (safe), либо в небезопасном (unsafe) контексте. Операторы, процедуры и функции, помеченные как небезопасные, запускаются вне области управления памятью с помощью сборщика мусора (Garbage Collector). Помните, что любой код C#, использующий указатели, требует для выполнения небезопасный контекст.
Для того, чтобы пометить небезопасный контекст (т. е. код), используется ключевое слово unsafe. Мы можем использовать unsafe двумя различными способами. Ключевое слово unsafe может использоваться как модификатор метода, свойства, конструктора, и т. д. Например:
Кроме того, ключевое слово unsafe может также использоваться, чтобы пометить группу операторов как небезопасную:
[Прикрепление объекта (Pinning an Object)]
Сборщик мусора C# может переместить объекты в памяти в соответствии с алгоритмом процесса уборки мусора. Язык C# предоставляет специальное ключевое слово fixed, чтобы указать сборщику мусора не перемещать объект. Это означает, что позиция переменной в памяти в памяти фиксируется, чтобы на нее мог ссылаться указатель. На C# это называется прикреплением (pinning).
Функционал оператора fixed обычно реализован путем генерации таблиц, описывающих для сборщика мусора, какие объекты в каких областях выполняемого кода должны оставаться фиксированными. Таким образом, пока процесс сбора мусора не встречает во время выполнения операторов fixed, потери ресурсов на них оказываются весьма незначительными. Однако, когда сборщик мусора встречает fixed, то фиксированные объекты могут привести к образованию фрагментации кучи (heap). Т. е. в куче могут появиться неиспользуемые «дыры». Следовательно, объекты должны использовать fixed только тогда, когда это абсолютно необходимо, и только на самый малый, насколько это возможно, промежуток времени выполнения кода.
[Указатели и методы (Pointers & Methods)]
Указатели могут быть переданы в метод как аргументы. Методы также могут возвратить указатель. Пример:
[Указатели и преобразования типа (Pointers & Conversions)]
Типы указателей в C# не наследуются от объекта, и нет существующих преобразований между типами указателя и объектами. Это означает, что boxing и un-boxing не поддерживается указателями. Однако C# поддерживает преобразования между различными типами указателей, типами указателей (pointer types) и целочисленными типами (integral types).
C# поддерживает и неявные (implicit), и явные (explicit) преобразования указателя в небезопасном контексте. Имеются неявные преобразования типа:
1. Из типа указателя на любой тип к типу указателя на тип void *.
2. Из типа null к любому другому типу указателя.
Оператор преобразования типа cast operator () необходим для любых явных преобразований типа. Имеются явные преобразования типа:
1. Из любого типа указателя на любой другой тип указателя.
2. Из типов sbyte, byte, short, ushort, int, uint, long, ulong к любому другому типу указателя.
3. Из любого типа указателя к типам sbyte, byte, short, ushort, int, uint, long, ulong.
[Арифметика указателей (Pointer Arithmetic)]
В небезопасном контексте операторы ++ и — могут быть приложены к переменной указателя всех типов, за исключением типа void *. Таким образом, для каждого типа указателя T* следующие операторы будут неявно перегружены (implicitly overloaded).
T* operator ++ (T *x);
T* operator — (T *x);
Оператор ++ добавляет sizeof(T) к адресу, содержащемуся в переменной указателя, и оператор — вычитает sizeof(T) из этого адреса для переменной указателя на тип T*.
In an un-safe context a constant can be added or subtracted from a pointer variable. Similarly a pointer variable can be subtracted from another pointer variable. But it is not possible to add two pointer variables in C#.
В небезопасном контексте операторы ==, !=, <, >, <=, >= могут также использоваться со значениями указателей на все типы. Умножение и деление переменной указателя на константу или другую переменную-указатель не поддерживается в C#.
[Выделение памяти стека (Stack Allocation)]
В небезопасном контексте локальные определения переменных могут включать инициализатор выделения стека (stack allocation initialiser), который выделяет память из стека вызовов (call stack).
Оператор stackalloc T[E] требует T как необрабатываемый (unmanaged) тип и E как выражение типа int. Вышеуказанная конструкция выделяет E * sizeof(T) байт из стека и генерирует указатель типа T* на новый выделенный блок. Если E отрицательно, то выбрасывается исключение System.OverFlowException. Если недостаточно памяти, то срабатывает исключение System.StackOverflowException.
Содержимое только что выделенной памяти является неопределенным. Нет способа неявного освобождения памяти, выделенной через stackalloc. Вместо этого весь блок памяти стека автоматически освобождается после возврата из функции.
[Указатели и массивы (Pointers & Arrays)]
В C# может быть получен доступ к элементам массива при помощи использованием нотаций указателя.
[Указатели и структуры (Pointers & Structures)]
Все структуры C# имеют тип переменной. На структуру тоже может быть задан указатель только в том случае, если структура в качестве своих полей содержит только типы в виде значения. Пример:
[Выводы]
1. Функции или процедуры C#, если они используют указатели, должны иметь атрибут unsafe.
2. Проект C#, в котором есть небезопасный (unsafe) код, должен иметь соответствующее разрешение в настройках (соответствует опции компилятора /unsafe).
3. Получение указателя от объекта возможно только в том случае, если он определен с атрибутом fixed.
[Как передать в функцию ссылку на массив данных в безопасном контексте?]
На языке C/C++ обычно массив большого размера передается через указатель. Однако в безопасном коде C# использовать указатели нельзя, возникает ошибка:
Для решения проблемы можно либо перевести проект в режим небезопасного (unsafe) кода, либо надо поменять синтаксис вызова функции:
std:: fixed, std:: scientific, std:: hexfloat, std:: defaultfloat
Modifies the default formatting for floating-point output.
This is an I/O manipulator, it may be called with an expression such as out << std :: fixed for any out of type std::basic_ostream (or with an expression such as in >> std :: scientific for any in of type std::basic_istream ).
Contents
[edit] Parameters
str | — | reference to I/O stream |
[edit] Return value
str (reference to the stream after manipulation)
[edit] Notes
Hexadecimal floating-point formatting ignores the stream precision specification, as required by the specification of std::num_put::do_put .
How does the ‘fixed’ keyword work?
Well it turns out that it’s a really nice example of collaboration between the main parts of the .NET runtime, here’s a list of all the components involved:
Now you could argue that all of these are required to execute any C# code, but what’s interesting about the fixed keyword is that they all have a specific part to play.
Compiler
To start with let’s look at one of the most basic scenarios for using the fixed keyword, directly accessing the contents of a C# string , (taken from a Roslyn unit test)
Which the compiler then turns into the following IL:
Note the pinned string V_1 that the compiler has created for us, it’s made a hidden local variable that holds a reference to the object we are using in the fixed statement, which in this case is the string “hello”. The purpose of this pinned local variable will be explained in a moment.
It’s also emitted an call to the OffsetToStringData getter method (from System.Runtime.CompilerServices.RuntimeHelpers ), which we will cover in more detail when we discuss the CLR’s role.
However, as an aside the compiler is also performing an optimisation for us, normally it would wrap the fixed statement in a finally block to ensure the pinned local variable is nulled out after controls leaves the scope. But in this case it has determined that is can leave out the finally statement entirely, from LocalRewriter_FixedStatement.cs in the Roslyn source:
What is this pinned identifier?
Let’s start by looking at the authoritative source, from Standard ECMA-335 Common Language Infrastructure (CLI)
II.7.1.2 pinned The signature encoding for pinned shall appear only in signatures that describe local variables (§II.15.4.1.3). While a method with a pinned local variable is executing, the VES shall not relocate the object to which the local refers. That is, if the implementation of the CLI uses a garbage collector that moves objects, the collector shall not move objects that are referenced by an active pinned local variable.
[Rationale: If unmanaged pointers are used to dereference managed objects, these objects shall be pinned. This happens, for example, when a managed object is passed to a method designed to operate with unmanaged data. end rationale]
VES = Virtual Execution System CLI = Common Language Infrastructure CTS = Common Type System
But if you prefer an explanation in more human readable form (i.e. not from a spec), then this extract from .Net IL Assembler Paperback by Serge Lidin is helpful:
Arguably the CLR has the easiest job to do (if you accept that it exists as a separate component from the JIT and GC), its job is to provide the offset of the raw string data via the OffsetToStringData method that is emitted by the compiler.
Now you might be thinking that this method does some complex calculations to determine the exact offset, but nope, it’s hard-coded!! (I told you that Strings and the CLR have a Special Relationship):
JITter
For the fixed keyword to work the role of the JITter is to provide information to the GC/Runtime about the lifetimes of variables within a method and in-particular if they are pinned locals. It does this via the GCInfo data it creates for every method:
To see this in action we have to enable the correct magic flags and then we will see the following:
See how in the section titled “Final local variable assignments” is had indicated that the V02 loc1 variable is must-init pinned and then down at the bottom is has this text:
Stack slot id for offset 32 (0x20) (sp) (pinned, untracked) = 0.
Aside: The JIT has also done some extra work for us and optimised away the call to OffsetToStringData by inlining it as the assembly code add rcx, 12 . On a slightly related note, previously the fixed keyword prevented a method from being inlined, but recently that changed, see Support inlining method with pinned locals for the full details.
Garbage Collector
Finally we come to the GC which has an important “role to play”, or “not to play” depending on which way you look at it.
In effect the GC has to get out of the way and leave the pinned local variable alone for the life-time of the method. Normally the GC is concerned about which objects are live or dead so that it knows what it has to clean up. But with pinned objects it has to go one step further, not only must it not clean up the object, but it must not move it around. Generally the GC likes to relocate objects around during the Compact Phase to make memory allocations cheap, but pinning prevents that as the object is being accessed via a pointer and therefore its memory address has to remain the same.
There is a great visual explanation of what that looks like from the excellent presentation CLR: Garbage Collection Inside Out by Maoni Stephens (click for full-sized version):
Note how the pinned blocks (marked with a ‘P’) have remained where they are, forcing the Gen 0/1/2 segments to start at awkard locations. This is why pinning too many objects and keeping them pinned for too long can cause GC overhead, it has to perform extra booking keeping and work around them.
In reality, when using the fixed keyword, your object will only remain pinned for a short period of time, i.e. until control leaves the scope. But if you are pinning object via the GCHandle class then the lifetime could be longer.
So to finish, let’s get the final word on pinning from Maoni Stephens, from Using GC Efficiently – Part 3 (read the blog post for more details):
- Pinning for a short time is cheap.
- Pinning an older object is not as harmful as pinning a young object.
- Creating pinned buffers that stay together instead of scattered around. This way you create fewer holes.
Summary
So that’s it, simple really!!
All the main parts of the .NET runtime do their bit and we get to use a handy feature that lets us drop-down and perform some bare-metal coding!!