небезопасный код может использоваться только при компиляции с параметром unsafe
небезопасный код (unsafe)
Помощь в написании контрольных, курсовых и дипломных работ здесь.
SharpDevelop и небезопасный код
Привет всем. Подскажите пожалуйста как разрешить небезопасный код в SharpDevelop Поиск по форуму.
Небезопасный код с использованием указателей (операции с массивами)
Задан стековый массив А(N). Получить из него массив В(M), содержащий элементы массива А, кратные.
Как разрешить небезопасный код в visual studio 2017 for mac
Как разрешить небезопасный код в вижле на маке, там в сборке нет этих параметров?
Добавлено через 1 минуту
ошибка CS0227: Небезопасный код может использоваться только при компиляции с параметром /unsafe
Добавлено через 1 минуту
ошибка CS0227: Небезопасный код может использоваться только при компиляции с параметром /unsafe
Еще раз внимательно читаем MSDN.
Помощь в написании контрольных, курсовых и дипломных работ здесь.
Как допустить компилировать небезопасный код в Visual Studio 2015?
В cmd также интересно как
Как убрать небезопасный код из функции подсчёта контрольной суммы?
Есть функция, представленная производителем приборов для проверки правильности приёма-передачи.
Unsafe code requires the `unsafe’ command line option to be specified
Как сказать ему что можно использовать unsafe код? (пишу в repl.it)
Переделать unsafe код в safe
Есть реализация криптоалгоритма Xtea с использованием unsafe кода. Как передалать его в safe.
Ненадежный код, типы указателей и указатели функций
Небезопасный код имеет следующие свойства:
типы указателей
В небезопасном контексте тип может быть не только типом значения или ссылочным типом, но и типом указателя. Объявления типа указателя выполняется одним из следующих способов:
Тип, указанный до * в типе указателя, называется ссылочным типом. Ссылочным типом может быть только неуправляемый тип.
При объявлении нескольких указателей в одном объявлении знак ( * ) указывается только с базовым типом. Он не используется в качестве префикса для каждого имени указателя. Пример:
Указатель не может указывать на ссылку или на структуру, содержащую ссылки, поскольку ссылка на объект может быть подвергнута сбору мусора, даже если на нее указывает указатель. Сборщик мусора не отслеживает наличие указателей любых типов, указывающих на объекты.
Оператор косвенного обращения указателя * можно использовать для доступа к содержимому, на которое указывает переменная-указатель. В качестве примера рассмотрим следующее объявление:
Для указателя типа void* использовать оператор косвенного обращения нельзя. Однако можно использовать приведение для преобразования указателя типа void в любой другой тип и наоборот.
В следующей таблице перечислены операторы, которые можно использовать для указателей в небезопасном контексте.
Дополнительные сведения об операторах, связанных с указателем, см. в разделе Операторы, связанные с указателем.
Буферы фиксированного размера
В безопасном коде структура C#, содержащая массив, не содержит элементы массива. Вместо этого в ней присутствуют ссылки на элементы. Вы можете внедрить массив фиксированного размера в структуру, если он используется в блоке небезопасного кода.
Размер следующего объекта struct не зависит от количества элементов в массиве, поскольку pathName представляет собой ссылку:
В предыдущем примере демонстрировался доступ к полям fixed без закрепления в памяти, доступный в C#, начиная с версии 7.3.
Еще одним распространенным массивом фиксированного размера является массив bool. Элементы в массиве bool всегда имеют размер в 1 байт. Массивы bool не подходят для создания битовых массивов или буферов.
Созданный компилятором код C# для Buffer помечен с помощью атрибутов, как показано далее.
Буферы фиксированного размера отличаются от обычных массивов указанными ниже особенностями.
Практическое руководство. Использование указателей для копирования массива байтов
В следующем примере указатели используются для копирования байт из одного массива в другой.
В этом примере доступ к элементам обоих массивов выполняется с помощью индексов, а не второго неуправляемого указателя. Объявление указателей pSource и pTarget закрепляет массивы. Эта возможность доступна начиная с C# 7.3.
Указатели функций
В приведенном выше коде иллюстрируется ряд правил работы с функциями, доступ к которым осуществляется по указателю:
Синтаксис имеет сходства с объявлением типов delegate и использованием указателей. Суффикс * в служебном слове delegate указывает на то, что данное объявление является указателем функции. Знак & при назначении группы методов указателю функции указывает, что операция использует адрес метода.
Дополнительные сведения об указателях функций см. в предложении Указатель функции для C# 9.0.
Параметры компилятора C# для правил языковых функций
CheckForOverflowUnderflow
Параметр CheckForOverflowUnderflow указывает, будет ли использование целочисленного арифметического оператора, в результате выполнения которого получено значение, выходящее за установленный для определенного типа данных диапазон значений, приводить к возникновению исключения во время выполнения.
AllowUnsafeBlocks
Дополнительные сведения см. в разделе Небезопасный код и указатели.
DefineConstants
Параметр DefineConstants определяет символы в файлах исходного кода вашей программы.
LangVersion
Инструктирует компилятор принимать только синтаксис, включенный в заданную спецификацию языка C#.
Допустимы следующие значения.
Значение | Значение |
---|---|
preview | Компилятор допускает использование любого допустимого синтаксиса языка из последней предварительной версии. |
latest | Компилятор принимает синтаксис из последней выпущенной версии компилятора (включая дополнительный номер версии). |
latestMajor ( default ) | Компилятор принимает синтаксис из последней основной версии компилятора. |
10.0 | Компилятор принимает только синтаксис, включенный в спецификацию C# 10.0 или более ранних версий. |
9.0 | Компилятор принимает только синтаксис, включенный в спецификацию C# 9.0 или более ранних версий. |
8.0 | Компилятор принимает только синтаксис, включенный в спецификацию C# 8.0 или более ранней версии. |
7.3 | Компилятор принимает только синтаксис, включенный в спецификацию C# 7.3 или более ранней версии. |
7.2 | Компилятор принимает только синтаксис, включенный в спецификацию C# 7.2 или более ранней версии. |
7.1 | Компилятор принимает только синтаксис, включенный в спецификацию C# 7.1 или более ранней версии. |
7 | Компилятор принимает только синтаксис, включенный в спецификацию C# 7.0 или более ранней версии. |
6 | Компилятор принимает только синтаксис, включенный в спецификацию C# 6.0 или более ранней версии. |
5 | Компилятор принимает только синтаксис, включенный в спецификацию C# 5.0 или более ранней версии. |
4 | Компилятор принимает только синтаксис, включенный в спецификацию C# 4.0 или более ранней версии. |
3 | Компилятор принимает только синтаксис, включенный в спецификацию C# 3.0 или более ранней версии. |
ISO-2 (или 2 ) | Компилятор принимает только синтаксис, включенный в спецификацию ISO/IEC 23270:2006 C# (2.0). |
ISO-1 (или 1 ) | Компилятор принимает только синтаксис, включенный в спецификацию ISO/IEC 23270:2003 C# (1.0/1.2). |
Версия языка по умолчанию зависит от целевой платформы приложения, а также от установленной версии пакета SDK или Visual Studio. Такие правила определены в статье Управление версиями языка C#.
Параметр компилятора LangVersion не влияет на метаданные, на которые ссылается ваше приложение C#.
Так как каждая версия компилятора C# содержит расширения для спецификации языка, параметр LangVersion не позволяет использовать возможности, аналогичные возможностям более ранней версии компилятора.
Другие способы указания версии языка C# см. в статье Управление версиями языка C#.
Дополнительные сведения об установке этого параметра компилятора программным путем см. в разделе LanguageVersion.
Спецификация языка C#
Минимальная версия пакета SDK, необходимая для поддержки всех возможностей языка
В следующей таблице перечислены минимальные версии пакета SDK с компилятором C#, поддерживающим соответствующую версию языка:
Допускает значения NULL
Параметр Nullable позволяет указать контекст, допускающий значение NULL.
Анализ потока используется для определения допустимости значений NULL в переменных в исполняемом коде. Выводимая допустимость переменной значения NULL не зависит от объявленной в переменной допустимости значения NULL. Вызовы методов анализируются, даже если они условно опущены. Например, Debug.Assert в режиме выпуска.
Вызов методов, снабженных следующими атрибутами, также повлияет на анализ потока:
Глобальный контекст, допускающий значения NULL, не применяется для созданных файлов кода. Независимо от этого параметра, контекст, допускающий значение NULL, отключен для любого исходного файла, помеченного как созданный. Существует четыре способа пометки файла как созданного:
Unsafe code
The core C# language, as defined in the preceding chapters, differs notably from C and C++ in its omission of pointers as a data type. Instead, C# provides references and the ability to create objects that are managed by a garbage collector. This design, coupled with other features, makes C# a much safer language than C or C++. In the core C# language it is simply not possible to have an uninitialized variable, a «dangling» pointer, or an expression that indexes an array beyond its bounds. Whole categories of bugs that routinely plague C and C++ programs are thus eliminated.
While practically every pointer type construct in C or C++ has a reference type counterpart in C#, nonetheless, there are situations where access to pointer types becomes a necessity. For example, interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm may not be possible or practical without access to pointers. To address this need, C# provides the ability to write unsafe code.
In unsafe code it is possible to declare and operate on pointers, to perform conversions between pointers and integral types, to take the address of variables, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program.
Unsafe contexts
The unsafe features of C# are available only in unsafe contexts. An unsafe context is introduced by including an unsafe modifier in the declaration of a type or member, or by employing an unsafe_statement:
The associated grammar productions are shown below.
the unsafe modifier specified in the struct declaration causes the entire textual extent of the struct declaration to become an unsafe context. Thus, it is possible to declare the Left and Right fields to be of a pointer type. The example above could also be written
Here, the unsafe modifiers in the field declarations cause those declarations to be considered unsafe contexts.
Other than establishing an unsafe context, thus permitting the use of pointer types, the unsafe modifier has no effect on a type or a member. In the example
The situation is slightly different when a pointer type is part of the method’s signature
Pointer types
In an unsafe context, a type (Types) may be a pointer_type as well as a value_type or a reference_type. However, a pointer_type may also be used in a typeof expression (Anonymous object creation expressions) outside of an unsafe context as such usage is not unsafe.
The type specified before the * in a pointer type is called the referent type of the pointer type. It represents the type of the variable to which a value of the pointer type points.
An unmanaged_type is any type that isn’t a reference_type or constructed type, and doesn’t contain reference_type or constructed type fields at any level of nesting. In other words, an unmanaged_type is one of the following:
The intuitive rule for mixing of pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references.
Some examples of pointer types are given in the table below:
Example | Description |
---|---|
byte* | Pointer to byte |
char* | Pointer to char |
int** | Pointer to pointer to int |
int*[] | Single-dimensional array of pointers to int |
void* | Pointer to unknown type |
For a given implementation, all pointer types must have the same size and representation.
Unlike C and C++, when multiple pointers are declared in the same declaration, in C# the * is written along with the underlying type only, not as a prefix punctuator on each pointer name. For example
A pointer_type cannot be used as a type argument (Constructed types), and type inference (Type inference) fails on generic method calls that would have inferred a type argument to be a pointer type.
A pointer_type may be used as the type of a volatile field (Volatile fields).
Although pointers can be passed as ref or out parameters, doing so can cause undefined behavior, since the pointer may well be set to point to a local variable which no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example:
A method can return a value of some type, and that type can be a pointer. For example, when given a pointer to a contiguous sequence of int s, that sequence’s element count, and some other int value, the following method returns the address of that value in that sequence, if a match occurs; otherwise it returns null :
In an unsafe context, several constructs are available for operating on pointers:
Fixed and moveable variables
The address-of operator (The address-of operator) and the fixed statement (The fixed statement) divide variables into two categories: Fixed variables and moveable variables.
Fixed variables reside in storage locations that are unaffected by operation of the garbage collector. (Examples of fixed variables include local variables, value parameters, and variables created by dereferencing pointers.) On the other hand, moveable variables reside in storage locations that are subject to relocation or disposal by the garbage collector. (Examples of moveable variables include fields in objects and elements of arrays.)
The & operator (The address-of operator) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using a fixed statement (The fixed statement), and that address remains valid only for the duration of that fixed statement.
In precise terms, a fixed variable is one of the following:
All other variables are classified as moveable variables.
Note that a static field is classified as a moveable variable. Also note that a ref or out parameter is classified as a moveable variable, even if the argument given for the parameter is a fixed variable. Finally, note that a variable produced by dereferencing a pointer is always classified as a fixed variable.
Pointer conversions
In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:
Additionally, in an unsafe context, the set of available explicit conversions (Explicit conversions) is extended to include the following explicit pointer conversions:
Finally, in an unsafe context, the set of standard implicit conversions (Standard implicit conversions) includes the following pointer conversion:
Conversions between two pointer types never change the actual pointer value. In other words, a conversion from one pointer type to another has no effect on the underlying address given by the pointer.
Consider the following case in which a variable having one type is accessed via a pointer to a different type:
When a pointer type is converted to a pointer to byte, the result points to the lowest addressed byte of the variable. Successive increments of the result, up to the size of the variable, yield pointers to the remaining bytes of that variable. For example, the following method displays each of the eight bytes in a double as a hexadecimal value:
Of course, the output produced depends on endianness.
Mappings between pointers and integers are implementation-defined. However, on 32* and 64-bit CPU architectures with a linear address space, conversions of pointers to or from integral types typically behave exactly like conversions of uint or ulong values, respectively, to or from those integral types.
Pointer arrays
In an unsafe context, arrays of pointers can be constructed. Only some of the conversions that apply to other array types are allowed on pointer arrays:
These restrictions mean that the expansion for the foreach statement over arrays described in The foreach statement cannot be applied to pointer arrays. Instead, a foreach statement of the form
Pointers in expressions
In an unsafe context, an expression may yield a result of a pointer type, but outside an unsafe context it is a compile-time error for an expression to be of a pointer type. In precise terms, outside an unsafe context a compile-time error occurs if any simple_name (Simple names), member_access (Member access), invocation_expression (Invocation expressions), or element_access (Element access) is of a pointer type.
In an unsafe context, the primary_no_array_creation_expression (Primary expressions) and unary_expression (Unary operators) productions permit the following additional constructs:
These constructs are described in the following sections. The precedence and associativity of the unsafe operators is implied by the grammar.
Pointer indirection
A pointer_indirection_expression consists of an asterisk ( * ) followed by a unary_expression.
If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined. Among the invalid values for dereferencing a pointer by the unary * operator are an address inappropriately aligned for the type pointed to (see example in Pointer conversions), and the address of a variable after the end of its lifetime.
For purposes of definite assignment analysis, a variable produced by evaluating an expression of the form *P is considered initially assigned (Initially assigned variables).
Pointer member access
Pointer element access
A pointer_element_access consists of a primary_no_array_creation_expression followed by an expression enclosed in » [ » and » ] «.
The pointer element access operator does not check for out-of-bounds errors and the behavior when accessing an out-of-bounds element is undefined. This is the same as C and C++.
The address-of operator
An addressof_expression consists of an ampersand ( & ) followed by a unary_expression.
The & operator does not require its argument to be definitely assigned, but following an & operation, the variable to which the operator is applied is considered definitely assigned in the execution path in which the operation occurs. It is the responsibility of the programmer to ensure that correct initialization of the variable actually does take place in this situation.
The rules of definite assignment for the & operator exist such that redundant initialization of local variables can be avoided. For example, many external APIs take a pointer to a structure which is filled in by the API. Calls to such APIs typically pass the address of a local struct variable, and without the rule, redundant initialization of the struct variable would be required.
Pointer increment and decrement
If a pointer increment or decrement operation overflows the domain of the pointer type, the result is implementation-defined, but no exceptions are produced.
Pointer arithmetic
which produces the output:
If a pointer arithmetic operation overflows the domain of the pointer type, the result is truncated in an implementation-defined fashion, but no exceptions are produced.
Pointer comparison
Because an implicit conversion exists from any pointer type to the void* type, operands of any pointer type can be compared using these operators. The comparison operators compare the addresses given by the two operands as if they were unsigned integers.
The sizeof operator
The sizeof operator returns the number of bytes occupied by a variable of a given type. The type specified as an operand to sizeof must be an unmanaged_type (Pointer types).
Expression | Result |
---|---|
sizeof(sbyte) | 1 |
sizeof(byte) | 1 |
sizeof(short) | 2 |
sizeof(ushort) | 2 |
sizeof(int) | 4 |
sizeof(uint) | 4 |
sizeof(long) | 8 |
sizeof(ulong) | 8 |
sizeof(char) | 2 |
sizeof(float) | 4 |
sizeof(double) | 8 |
sizeof(bool) | 1 |
For all other types, the result of the sizeof operator is implementation-defined and is classified as a value, not a constant.
The order in which members are packed into a struct is unspecified.
For alignment purposes, there may be unnamed padding at the beginning of a struct, within a struct, and at the end of the struct. The contents of the bits used as padding are indeterminate.
When applied to an operand that has struct type, the result is the total number of bytes in a variable of that type, including any padding.
The fixed statement
In an unsafe context, the embedded_statement (Statements) production permits an additional construct, the fixed statement, which is used to «fix» a moveable variable such that its address remains constant for the duration of the statement.
A fixed_pointer_initializer can be one of the following:
For each address computed by a fixed_pointer_initializer the fixed statement ensures that the variable referenced by the address is not subject to relocation or disposal by the garbage collector for the duration of the fixed statement. For example, if the address computed by a fixed_pointer_initializer references a field of an object or an element of an array instance, the fixed statement guarantees that the containing object instance is not relocated or disposed of during the lifetime of the statement.
It is the programmer’s responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. For example, when pointers created by fixed statements are passed to external APIs, it is the programmer’s responsibility to ensure that the APIs retain no memory of these pointers.
Fixed objects may cause fragmentation of the heap (because they can’t be moved). For that reason, objects should be fixed only when absolutely necessary and then only for the shortest amount of time possible.
demonstrates several uses of the fixed statement. The first statement fixes and obtains the address of a static field, the second statement fixes and obtains the address of an instance field, and the third statement fixes and obtains the address of an array element. In each case it would have been an error to use the regular & operator since the variables are all classified as moveable variables.
The fourth fixed statement in the example above produces a similar result to the third.
This example of the fixed statement uses string :
which produces the output:
a fixed statement is used to fix an array so its address can be passed to a method that takes a pointer.
a fixed statement is used to fix a fixed size buffer of a struct so its address can be used as a pointer.
Modifying objects of managed type through fixed pointers can results in undefined behavior. For example, because strings are immutable, it is the programmer’s responsibility to ensure that the characters referenced by a pointer to a fixed string are not modified.
Fixed size buffers
Fixed size buffers are used to declare «C style» in-line arrays as members of structs, and are primarily useful for interfacing with unmanaged APIs.
Fixed size buffer declarations
A fixed size buffer is a member that represents storage for a fixed length buffer of variables of a given type. A fixed size buffer declaration introduces one or more fixed size buffers of a given element type. Fixed size buffers are only permitted in struct declarations and can only occur in unsafe contexts (Unsafe contexts).
A fixed size buffer declaration may include a set of attributes (Attributes), a new modifier (Modifiers), a valid combination of the four access modifiers (Type parameters and constraints) and an unsafe modifier (Unsafe contexts). The attributes and modifiers apply to all of the members declared by the fixed size buffer declaration. It is an error for the same modifier to appear multiple times in a fixed size buffer declaration.
A fixed size buffer declaration is not permitted to include the static modifier.
The elements of a fixed size buffer are guaranteed to be laid out sequentially in memory.
A fixed size buffer declaration that declares multiple fixed size buffers is equivalent to multiple declarations of a single fixed size buffer declaration with the same attributes, and element types. For example
Fixed size buffers in expressions
Member lookup (Operators) of a fixed size buffer member proceeds exactly like member lookup of a field.
A fixed size buffer can be referenced in an expression using a simple_name (Type inference) or a member_access (Compile-time checking of dynamic overload resolution).
The subsequent elements of the fixed size buffer can be accessed using pointer operations from the first element. Unlike access to arrays, access to the elements of a fixed size buffer is an unsafe operation and is not range checked.
The following example declares and uses a struct with a fixed size buffer member.
Definite assignment checking
Fixed size buffers are not subject to definite assignment checking (Definite assignment), and fixed size buffer members are ignored for purposes of definite assignment checking of struct type variables.
When the outermost containing struct variable of a fixed size buffer member is a static variable, an instance variable of a class instance, or an array element, the elements of the fixed size buffer are automatically initialized to their default values (Default values). In all other cases, the initial content of a fixed size buffer is undefined.
Stack allocation
In an unsafe context, a local variable declaration (Local variable declarations) may include a stack allocation initializer which allocates memory from the call stack.
The unmanaged_type indicates the type of the items that will be stored in the newly allocated location, and the expression indicates the number of these items. Taken together, these specify the required allocation size. Since the size of a stack allocation cannot be negative, it is a compile-time error to specify the number of items as a constant_expression that evaluates to a negative value.
The content of the newly allocated memory is undefined.
Stack allocation initializers are not permitted in catch or finally blocks (The try statement).
a stackalloc initializer is used in the IntToString method to allocate a buffer of 16 characters on the stack. The buffer is automatically discarded when the method returns.
Dynamic memory allocation
Except for the stackalloc operator, C# provides no predefined constructs for managing non-garbage collected memory. Such services are typically provided by supporting class libraries or imported directly from the underlying operating system. For example, the Memory class below illustrates how the heap functions of an underlying operating system might be accessed from C#:
An example that uses the Memory class is given below: