Unsafe 程式碼Unsafe code

如先前章節中所定義的核心 c # 語言,與 C 和 c + + 的差異在於它不會將指標省略為資料類型。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. 相反地,c # 會提供參考,以及建立由垃圾收集行程所管理之物件的能力。Instead, C# provides references and the ability to create objects that are managed by a garbage collector. 這種設計與其他功能結合,使 c # 比 C 或 c + + 更安全。This design, coupled with other features, makes C# a much safer language than C or C++. 在核心 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. 因此會消除定期災禍 C 和 c + + 程式之錯誤的整個類別。Whole categories of bugs that routinely plague C and C++ programs are thus eliminated.

實際上,C 或 c + + 中的每個指標類型都有 c # 中的參考型別,不過在某些情況下,指標類型的存取會成為必要的。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. 為了滿足這種需求,c # 提供撰寫 unsafe 程式碼 的能力。To address this need, C# provides the ability to write unsafe code.

在 unsafe 程式碼中,可以宣告和操作指標、在指標和整數類型之間執行轉換,以取得變數的位址等等。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. 就概念而言,撰寫 unsafe 程式碼和在 c # 程式內撰寫 C 程式碼很類似。In a sense, writing unsafe code is much like writing C code within a C# program.

Unsafe 程式碼實際上是從開發人員和使用者角度來看的「安全」功能。Unsafe code is in fact a "safe" feature from the perspective of both developers and users. Unsafe 程式碼必須清楚地以修飾 unsafe 詞標記,因此開發人員可能不小心使用不安全的功能,且執行引擎可確保不受信任的程式碼無法在不受信任的環境中執行。Unsafe code must be clearly marked with the modifier unsafe, so developers can't possibly use unsafe features accidentally, and the execution engine works to ensure that unsafe code cannot be executed in an untrusted environment.

不安全的內容Unsafe contexts

C # 的 unsafe 功能只適用于 unsafe 內容。The unsafe features of C# are available only in unsafe contexts. 不安全的內容是藉由 unsafe 在型別或成員的宣告中包含修飾詞,或是採用 unsafe_statement 來引入:An unsafe context is introduced by including an unsafe modifier in the declaration of a type or member, or by employing an unsafe_statement:

  • 類別、結構、介面或委派的宣告可能包含 unsafe 修飾詞,在這種情況下,該類型宣告的整個文字範圍 (包括類別、結構或介面的主體,) 會被視為不安全的內容。A declaration of a class, struct, interface, or delegate may include an unsafe modifier, in which case the entire textual extent of that type declaration (including the body of the class, struct, or interface) is considered an unsafe context.
  • 欄位、方法、屬性、事件、索引子、運算子、實例的函式、函式或靜態函式的宣告可能包含 unsafe 修飾詞,在這種情況下,該成員宣告的整個文字範圍會被視為不安全的內容。A declaration of a field, method, property, event, indexer, operator, instance constructor, destructor, or static constructor may include an unsafe modifier, in which case the entire textual extent of that member declaration is considered an unsafe context.
  • Unsafe_statement 可讓您在 區塊 內使用 unsafe 內容。An unsafe_statement enables the use of an unsafe context within a block. 相關 區塊 的整個文字範圍會被視為不安全的內容。The entire textual extent of the associated block is considered an unsafe context.

相關的文法生產如下所示。The associated grammar productions are shown below.

class_modifier_unsafe
    : 'unsafe'
    ;

struct_modifier_unsafe
    : 'unsafe'
    ;

interface_modifier_unsafe
    : 'unsafe'
    ;

delegate_modifier_unsafe
    : 'unsafe'
    ;

field_modifier_unsafe
    : 'unsafe'
    ;

method_modifier_unsafe
    : 'unsafe'
    ;

property_modifier_unsafe
    : 'unsafe'
    ;

event_modifier_unsafe
    : 'unsafe'
    ;

indexer_modifier_unsafe
    : 'unsafe'
    ;

operator_modifier_unsafe
    : 'unsafe'
    ;

constructor_modifier_unsafe
    : 'unsafe'
    ;

destructor_declaration_unsafe
    : attributes? 'extern'? 'unsafe'? '~' identifier '(' ')' destructor_body
    | attributes? 'unsafe'? 'extern'? '~' identifier '(' ')' destructor_body
    ;

static_constructor_modifiers_unsafe
    : 'extern'? 'unsafe'? 'static'
    | 'unsafe'? 'extern'? 'static'
    | 'extern'? 'static' 'unsafe'?
    | 'unsafe'? 'static' 'extern'?
    | 'static' 'extern'? 'unsafe'?
    | 'static' 'unsafe'? 'extern'?
    ;

embedded_statement_unsafe
    : unsafe_statement
    | fixed_statement
    ;

unsafe_statement
    : 'unsafe' block
    ;

在範例中In the example

public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

unsafe在結構宣告中指定的修飾詞會導致結構宣告的整個文字範圍成為 unsafe 內容。the unsafe modifier specified in the struct declaration causes the entire textual extent of the struct declaration to become an unsafe context. 因此,您可以將 LeftRight 欄位宣告為指標類型。Thus, it is possible to declare the Left and Right fields to be of a pointer type. 您也可以撰寫上述範例The example above could also be written

public struct Node
{
    public int Value;
    public unsafe Node* Left;
    public unsafe Node* Right;
}

在此,欄位宣告中的修飾詞 unsafe 會將這些宣告視為 unsafe 內容。Here, the unsafe modifiers in the field declarations cause those declarations to be considered unsafe contexts.

除了建立不安全的內容,因此允許使用指標類型之外,此 unsafe 修飾詞不會影響型別或成員。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

public class A
{
    public unsafe virtual void F() {
        char* p;
        ...
    }
}

public class B: A
{
    public override void F() {
        base.F();
        ...
    }
}

unsafe方法中的修飾詞 F A 只會使的文字範圍 F 成為不安全的內容,在此內容中可以使用語言的 unsafe 功能。the unsafe modifier on the F method in A simply causes the textual extent of F to become an unsafe context in which the unsafe features of the language can be used. 在的覆寫 FB ,不需要重新指定修飾詞, unsafe 除非 F 在本身中,方法 B 本身需要存取不安全的功能。In the override of F in B, there is no need to re-specify the unsafe modifier -- unless, of course, the F method in B itself needs access to unsafe features.

當指標類型是方法簽章的一部分時,此情況會略有不同The situation is slightly different when a pointer type is part of the method's signature

public unsafe class A
{
    public virtual void F(char* p) {...}
}

public class B: A
{
    public unsafe override void F(char* p) {...}
}

在這裡,因為的簽章 F 包含指標類型,所以只能寫入 unsafe 內容中。Here, because F's signature includes a pointer type, it can only be written in an unsafe context. 但是,不安全的內容可以藉由讓整個類別不安全(如同中的案例), A 或是在方法宣告中包含修飾詞來引入 unsafe ,如同中的情況 BHowever, the unsafe context can be introduced by either making the entire class unsafe, as is the case in A, or by including an unsafe modifier in the method declaration, as is the case in B.

指標類型Pointer types

在 unsafe 內容中, 類型 (類型) 可能是 pointer_type 以及 value_typereference_typeIn an unsafe context, a type (Types) may be a pointer_type as well as a value_type or a reference_type. 不過, pointer_type 也可以用在運算式中, typeof (在不安全的內容外) 的 匿名物件建立運算式 ,因為這類使用方式不安全。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.

type_unsafe
    : pointer_type
    ;

Pointer_type 是以 unmanaged_type 或關鍵字的形式寫入 void ,後面接著 * 標記:A pointer_type is written as an unmanaged_type or the keyword void, followed by a * token:

pointer_type
    : unmanaged_type '*'
    | 'void' '*'
    ;

unmanaged_type
    : type
    ;

指標型別中的之前指定的型別 * 稱為指標型別的 引用型 別。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.

不同于參考型別 (值) ,垃圾收集行程不會追蹤指標,因為垃圾收集行程並不知道指標和其指向的資料。Unlike references (values of reference types), pointers are not tracked by the garbage collector -- the garbage collector has no knowledge of pointers and the data to which they point. 基於這個理由,指標不允許指向參考或包含參考的結構,而且指標的參考型別必須是 unmanaged_typeFor this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer must be an unmanaged_type.

Unmanaged_type 是任何不是 reference_type 或結構化類型的類型,而且不包含在任何嵌套層級的 reference_type 或結構化類型欄位。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. 換句話說, unmanaged_type 是下列其中一項:In other words, an unmanaged_type is one of the following:

  • sbyte、、、、、、、、、、、 byte short ushort int uint long ulong char float double decimalboolsbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.
  • 任何 enum_typeAny enum_type.
  • 任何 pointer_typeAny pointer_type.
  • 任何不是結構化型別的使用者定義 struct_type ,而且只包含 unmanaged_type 的欄位。Any user-defined struct_type that is not a constructed type and contains fields of unmanaged_type s only.

混合指標和參考的直覺規則是,參考 (物件) 的物件可包含指標,但是指標的表示不允許包含參考。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* 指標 bytePointer to byte
char* 指標 charPointer to char
int** 指向指標的指標 intPointer to pointer to int
int*[] 指標的一維陣列 intSingle-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.

不同于 C 和 c + +,在相同的宣告中宣告多個指標時,在 c # 中, * 只會與基礎類型一起寫入,而不是以每個指標名稱的前置詞標點符號。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

int* pi, pj;    // NOT as int *pi, *pj;

具有類型之指標的值 T* 代表型別變數的位址 TThe value of a pointer having type T* represents the address of a variable of type T. 指標間接運算子 * (指標間接 運算子,) 可用來存取此變數。The pointer indirection operator * (Pointer indirection) may be used to access this variable. 例如,假設有 P 型別的變數 int* ,則運算式表示在所 *P 包含的 int 位址中找到的變數 PFor example, given a variable P of type int*, the expression *P denotes the int variable found at the address contained in P.

如同物件參考,指標可能是 nullLike an object reference, a pointer may be null. 將間接取值運算子套用至 null 指標會導致執行定義的行為。Applying the indirection operator to a null pointer results in implementation-defined behavior. 具有值的指標 null 會以所有位零表示。A pointer with value null is represented by all-bits-zero.

void*類型代表未知類型的指標。The void* type represents a pointer to an unknown type. 因為參考型別未知,所以間接取值運算子無法套用至類型的指標 void* ,也不能在這類指標上執行任何算術。Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type void*, nor can any arithmetic be performed on such a pointer. 不過,類型的指標 void* 可以轉換成任何其他指標類型 (反之亦然) 。However, a pointer of type void* can be cast to any other pointer type (and vice versa).

指標類型是不同的類型類別。Pointer types are a separate category of types. 不同于參考型別和實值型別,指標型別不會繼承自, object 而且指標類型與之間不會有轉換存在 objectUnlike reference types and value types, pointer types do not inherit from object and no conversions exist between pointer types and object. 尤其是,指標不支援將 (的裝箱和取消裝箱) 。In particular, boxing and unboxing (Boxing and unboxing) are not supported for pointers. 不過,在不同的指標類型之間以及指標類型與整數類之間,允許轉換。However, conversions are permitted between different pointer types and between pointer types and the integral types. 指標轉換中會說明這一點。This is described in Pointer conversions.

Pointer_type 不能當做型別引數 使用 ()的型別引數,而且 (別推斷的型別推斷) 在將型別引數推斷為指標類型的泛型方法呼叫上失敗。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.

Pointer_type 可以用來做為 volatile 欄位的類型, (volatile 欄位) 。A pointer_type may be used as the type of a volatile field (Volatile fields).

雖然可以將指標當做 refout 參數傳遞,但這麼做可能會導致未定義的行為,因為指標可能會設定為指向本機變數,而當呼叫的方法傳回時,或它所用的固定物件無法再修正。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:

using System;

class Test
{
    static int value = 20;

    unsafe static void F(out int* pi1, ref int* pi2) {
        int i = 10;
        pi1 = &i;

        fixed (int* pj = &value) {
            // ...
            pi2 = pj;
        }
    }

    static void Main() {
        int i = 10;
        unsafe {
            int* px1;
            int* px2 = &i;

            F(out px1, ref px2);

            Console.WriteLine("*px1 = {0}, *px2 = {1}",
                *px1, *px2);    // undefined behavior
        }
    }
}

方法可以傳回某種類型的值,而且該類型可以是指標。A method can return a value of some type, and that type can be a pointer. 例如,當指定連續序列的指標時 int ,該序列的專案計數和其他 int 值,如果發生相符專案,下列方法會傳回該序列中該值的位址; 否則會傳回 nullFor example, when given a pointer to a contiguous sequence of ints, 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:

unsafe static int* Find(int* pi, int size, int value) {
    for (int i = 0; i < size; ++i) {
        if (*pi == value) 
            return pi;
        ++pi;
    }
    return null;
}

在 unsafe 內容中,有數個可在指標上操作的結構:In an unsafe context, several constructs are available for operating on pointers:

固定和可移動變數Fixed and moveable variables

Address 運算子 (address of 運算子) 和 fixed (fixed 語句 的語句) 將變數分割成兩個類別: *固定變數 _ 和 _ 可移動變數 *。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.)

&運算子會 (address 運算子) 允許取得固定變數的位址而不受限制。The & operator (The address-of operator) permits the address of a fixed variable to be obtained without restrictions. 不過,由於可移動變數可能會由垃圾收集行程重新配置或處置,可移動變數的位址只能使用 fixed (fixed 語句) 的語句取得,而該位址只會在該語句的持續期間內保持有效 fixedHowever, 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:

  • 除非匿名函式已捕捉到變數,否則為 simple_name (簡單名稱) 所產生的變數。A variable resulting from a simple_name (Simple names) that refers to a local variable or a value parameter, unless the variable is captured by an anonymous function.
  • 因為表單 member_access (成員存取) 所產生的變數 V.I ,其中 Vstruct_type 的固定變數。A variable resulting from a member_access (Member access) of the form V.I, where V is a fixed variable of a struct_type.
  • 從表單 pointer_indirection_expression (指標間接) *P 、表單 pointer_member_access (指標成員存取) P->I ,或是表單 pointer_element_access (指標專案存取) 所產生的變數 P[E]A variable resulting from a pointer_indirection_expression (Pointer indirection) of the form *P, a pointer_member_access (Pointer member access) of the form P->I, or a pointer_element_access (Pointer element access) of the form P[E].

所有其他變數都會分類為可移動變數。All other variables are classified as moveable variables.

請注意,靜態欄位會分類為可移動變數。Note that a static field is classified as a moveable variable. 另請注意, ref out 即使為參數提供的引數是固定的變數,或參數也會分類為可移動變數。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

在 unsafe 內容中,可用的隱含轉換集 (隱含轉換) 擴充成包含下列隱含指標轉換:In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:

  • 從任何 pointer_type 到型別 void*From any pointer_type to the type void*.
  • null 常值到任何 pointer_typeFrom the null literal to any pointer_type.

此外,在 unsafe 內容中,可用的明確轉換集合 (明確的轉換) 已擴充為包含下列明確的指標轉換:Additionally, in an unsafe context, the set of available explicit conversions (Explicit conversions) is extended to include the following explicit pointer conversions:

  • 從任何 pointer_type 到任何其他 pointer_typeFrom any pointer_type to any other pointer_type.
  • 從、、、、、、 sbyte byte short ushort int uint longulong 到任何 pointer_typeFrom sbyte, byte, short, ushort, int, uint, long, or ulong to any pointer_type.
  • 從任何 pointer_typesbytebyteshort 、、、、 ushort int uint longulongFrom any pointer_type to sbyte, byte, short, ushort, int, uint, long, or ulong.

最後,在 unsafe 內容中,標準隱含轉換 (標準隱含轉換) 包含下列指標轉換:Finally, in an unsafe context, the set of standard implicit conversions (Standard implicit conversions) includes the following pointer conversion:

  • 從任何 pointer_type 到型別 void*From any pointer_type to the type void*.

兩個指標類型之間的轉換絕對不會變更實際的指標值。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.

當某個指標類型轉換成另一個指標類型時,如果所指向型別的結果指標未正確對齊,如果結果已取值,則行為會是未定義的。When one pointer type is converted to another, if the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined if the result is dereferenced. 一般情況下,「正確對齊」的概念是可轉移的:如果類型的指標已正確對齊型別的指標,而該指標的指標已正確對齊類型的指標, A B 則類型 C 的指標 A 會正確對齊 C 類型的指標。In general, the concept "correctly aligned" is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which, in turn, is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.

請考慮下列案例,其中有一個類型的變數是透過不同類型的指標來存取:Consider the following case in which a variable having one type is accessed via a pointer to a different type:

char c = 'A';
char* pc = &c;
void* pv = pc;
int* pi = (int*)pv;
int i = *pi;         // undefined
*pi = 123456;        // undefined

當指標類型轉換為位元組的指標時,結果會指向變數的最小定址位元組。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:

using System;

class Test
{
    unsafe static void Main() {
      double d = 123.456e23;
        unsafe {
           byte* pb = (byte*)&d;
            for (int i = 0; i < sizeof(double); ++i)
               Console.Write("{0:X2} ", *pb++);
            Console.WriteLine();
        }
    }
}

當然,所產生的輸出取決於位元組順序。Of course, the output produced depends on endianness.

指標與整數之間的對應是實作為定義。Mappings between pointers and integers are implementation-defined. 不過,在具有線性位址空間的 32 * 和64位 CPU 架構上,從整數類資料類型轉換的轉換,通常會與 uint ulong 這些整數類型之間的轉換或值的轉換方式完全相同。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

在 unsafe 內容中,可以構造指標的陣列。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:

  • 隱含參考轉換 (從任何 array_type 到的 隱含參考轉換) System.Array ,而且它所執行的介面也適用于指標陣列。The implicit reference conversion (Implicit reference conversions) from any array_type to System.Array and the interfaces it implements also applies to pointer arrays. 不過,嘗試透過或它所執行的介面來存取陣列元素的任何嘗試 System.Array ,都會在執行時間產生例外狀況,因為指標類型無法轉換成 objectHowever, any attempt to access the array elements through System.Array or the interfaces it implements will result in an exception at run-time, as pointer types are not convertible to object.
  • 隱含和明確參考轉換 (隱含參考轉換、從一維陣列型別) 的 明確參考轉換S[] System.Collections.Generic.IList<T> 以及其泛型基底介面永遠不會套用至指標陣列,因為指標類型不能當做型別引數使用,也不會將指標類型轉換為非指標類型。The implicit and explicit reference conversions (Implicit reference conversions, Explicit reference conversions) from a single-dimensional array type S[] to System.Collections.Generic.IList<T> and its generic base interfaces never apply to pointer arrays, since pointer types cannot be used as type arguments, and there are no conversions from pointer types to non-pointer types.
  • 明確的參考轉換 () 的 明確參考轉換 System.Array ,而它所執行的介面 array_type 會套用至指標陣列。The explicit reference conversion (Explicit reference conversions) from System.Array and the interfaces it implements to any array_type applies to pointer arrays.
  • 明確參考轉換 () 的 明確參考轉換System.Collections.Generic.IList<S> 而且其基底介面不會 T[] 套用至指標陣列,因為指標類型不能當做型別引數使用,也不會從指標類型轉換為非指標類型。The explicit reference conversions (Explicit reference conversions) from System.Collections.Generic.IList<S> and its base interfaces to a single-dimensional array type T[] never applies to pointer arrays, since pointer types cannot be used as type arguments, and there are no conversions from pointer types to non-pointer types.

這些限制表示 foreach foreach 語句 中所描述之陣列的擴充無法套用至指標陣列。These restrictions mean that the expansion for the foreach statement over arrays described in The foreach statement cannot be applied to pointer arrays. 相反地,表單的 foreach 語句Instead, a foreach statement of the form

foreach (V v in x) embedded_statement

其中的型別 x 是表單的陣列型別 T[,,...,] ,而 N 維度的數目減1, TV 是指標類型,則是使用嵌套的 for 迴圈來展開,如下所示:where the type of x is an array type of the form T[,,...,], N is the number of dimensions minus 1 and T or V is a pointer type, is expanded using nested for-loops as follows:

{
    T[,,...,] a = x;
    for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
    for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
    ...
    for (int iN = a.GetLowerBound(N); iN <= a.GetUpperBound(N); iN++) {
        V v = (V)a.GetValue(i0,i1,...,iN);
        embedded_statement
    }
}

a embedded_statement 或 i0 程式的 i1 iN x 任何其他原始程式碼都看不到或可存取變數(、、...)。The variables a, i0, i1, ..., iN are not visible to or accessible to x or the embedded_statement or any other source code of the program. v 內嵌語句中,變數是唯讀的。The variable v is read-only in the embedded statement. 如果沒有明確的轉換 (指標轉換) 從 T) 的專案類型 (,則會 V 產生錯誤,而且不會採取進一步的步驟。If there is not an explicit conversion (Pointer conversions) from T (the element type) to V, an error is produced and no further steps are taken. 如果 x 有值 nullSystem.NullReferenceException 則會在執行時間擲回。If x has the value null, a System.NullReferenceException is thrown at run-time.

運算式中的指標Pointers in expressions

在 unsafe 內容中,運算式可能會產生指標類型的結果,但在不安全的內容之外,它是指標類型之運算式的編譯時期錯誤。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. 確切的說,在不安全的內容之外,如果有任何 simple_name (簡單名稱) 、 member_access (成員存取 ) 、invocation_expression ( 調用運算式) ,或 element_access (專案 存取) 是指標類型,就會發生編譯時期錯誤。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.

在 unsafe 內容中, primary_no_array_creation_expression (主要運算式) 和 unary_expression (一元運算子) 的生產會允許下列其他結構:In an unsafe context, the primary_no_array_creation_expression (Primary expressions) and unary_expression (Unary operators) productions permit the following additional constructs:

primary_no_array_creation_expression_unsafe
    : pointer_member_access
    | pointer_element_access
    | sizeof_expression
    ;

unary_expression_unsafe
    : pointer_indirection_expression
    | addressof_expression
    ;

下列各節將說明這些結構。These constructs are described in the following sections. Unsafe 運算子的優先順序和關聯性是由文法所隱含。The precedence and associativity of the unsafe operators is implied by the grammar.

指標間接Pointer indirection

Pointer_indirection_expression 包含星號 (*) 後面接著 unary_expressionA pointer_indirection_expression consists of an asterisk (*) followed by a unary_expression.

pointer_indirection_expression
    : '*' unary_expression
    ;

一元 * 運算子代表指標間接取值,可用來取得指標指向的變數。The unary * operator denotes pointer indirection and is used to obtain the variable to which a pointer points. 評估的結果 *P (其中 P 是指標類型的運算式 T* )是類型的變數 TThe result of evaluating *P, where P is an expression of a pointer type T*, is a variable of type T. 將一元 * 運算子套用至類型的運算式或非指標類型的運算式時,會發生編譯時期錯誤 void*It is a compile-time error to apply the unary * operator to an expression of type void* or to an expression that isn't of a pointer type.

將一元 * 運算子套用至指標的效果 null 是實作為定義。The effect of applying the unary * operator to a null pointer is implementation-defined. 尤其是,並不保證此作業會擲回 System.NullReferenceExceptionIn particular, there is no guarantee that this operation throws a System.NullReferenceException.

如果將不正確值指派給指標,一元運算子的行為 * 會是未定義的。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.

基於明確指派分析的目的,評估表單運算式所產生的變數會被 *P 視為最初指派 (初始指派的變數) 。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_member_access 包含 primary_expression,後面接著 " -> " 權杖,後面接著 識別碼 和選擇性的 type_argument_listA pointer_member_access consists of a primary_expression, followed by a "->" token, followed by an identifier and an optional type_argument_list.

pointer_member_access
    : primary_expression '->' identifier
    ;

在表單的指標成員存取中 P->IP 必須是指標類型的運算式,而不是 void* ,且 I 必須表示所指向之類型的可存取成員 PIn a pointer member access of the form P->I, P must be an expression of a pointer type other than void*, and I must denote an accessible member of the type to which P points.

表單的指標成員存取權 P->I 會與完全相同的評估 (*P).IA pointer member access of the form P->I is evaluated exactly as (*P).I. 如需指標間接運算子的描述 (*) ,請參閱 指標間接取值。For a description of the pointer indirection operator (*), see Pointer indirection. 如需成員存取運算子的描述 (.) ,請參閱 成員存取For a description of the member access operator (.), see Member access.

在範例中In the example

using System;

struct Point
{
    public int x;
    public int y;

    public override string ToString() {
        return "(" + x + "," + y + ")";
    }
}

class Test
{
    static void Main() {
        Point point;
        unsafe {
            Point* p = &point;
            p->x = 10;
            p->y = 20;
            Console.WriteLine(p->ToString());
        }
    }
}

->運算子用來存取欄位,並透過指標叫用結構的方法。the -> operator is used to access fields and invoke a method of a struct through a pointer. 因為此作業 P->I 相當於,所以 (*P).IMain 方法的撰寫方式也很好:Because the operation P->I is precisely equivalent to (*P).I, the Main method could equally well have been written:

class Test
{
    static void Main() {
        Point point;
        unsafe {
            Point* p = &point;
            (*p).x = 10;
            (*p).y = 20;
            Console.WriteLine((*p).ToString());
        }
    }
}

指標元素存取Pointer element access

Pointer_element_access 包含 primary_no_array_creation_expression ,後面接著以 " [ " 和 "" 括住的運算式 ]A pointer_element_access consists of a primary_no_array_creation_expression followed by an expression enclosed in "[" and "]".

pointer_element_access
    : primary_no_array_creation_expression '[' expression ']'
    ;

在表單的指標專案存取中 P[E]P 必須是指標類型的運算式,而不是 void* ,而且 E 必須是可以隱含轉換成 int 、、或的運算式 uint long ulongIn a pointer element access of the form P[E], P must be an expression of a pointer type other than void*, and E must be an expression that can be implicitly converted to int, uint, long, or ulong.

表單的指標元素存取權 P[E] 會與完全相同的評估 *(P + E)A pointer element access of the form P[E] is evaluated exactly as *(P + E). 如需指標間接運算子的描述 (*) ,請參閱 指標間接取值。For a description of the pointer indirection operator (*), see Pointer indirection. 如需 () 之指標加法運算子的描述 + ,請參閱 指標算術For a description of the pointer addition operator (+), see Pointer arithmetic.

在範例中In the example

class Test
{
    static void Main() {
        unsafe {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++) p[i] = (char)i;
        }
    }
}

指標專案存取用來初始化迴圈中的字元緩衝區 fora pointer element access is used to initialize the character buffer in a for loop. 因為此作業 P[E] 相當於,所以 *(P + E) 也可以撰寫範例:Because the operation P[E] is precisely equivalent to *(P + E), the example could equally well have been written:

class Test
{
    static void Main() {
        unsafe {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++) *(p + i) = (char)i;
        }
    }
}

指標元素存取運算子不會檢查超出範圍的錯誤,以及存取超出界限的元素時的行為未定義。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. 這與 C 和 c + + 相同。This is the same as C and C++.

傳址運算子The address-of operator

Addressof_expression 是由連字號 (&) 後面接著 unary_expression 所組成。An addressof_expression consists of an ampersand (&) followed by a unary_expression.

addressof_expression
    : '&' unary_expression
    ;

假設有一個型別為的運算式, E T 並將其分類為固定變數 (固定和可移動變數) ,則結構會 &E 計算所指定之變數的位址 EGiven an expression E which is of a type T and is classified as a fixed variable (Fixed and moveable variables), the construct &E computes the address of the variable given by E. 結果的型別是 T* ,而且會分類為值。The type of the result is T* and is classified as a value. 如果分類 E E 為唯讀區域變數,或表示可移動變數,則會發生編譯時期錯誤(如果未分類為變數的話) EA compile-time error occurs if E is not classified as a variable, if E is classified as a read-only local variable, or if E denotes a moveable variable. 在最後一個案例中,fixed 語句 (fixed 語句) 可以用來在取得其位址之前暫時「修正」變數。In the last case, a fixed statement (The fixed statement) can be used to temporarily "fix" the variable before obtaining its address. 如同 成員存取中所述,在實例的函式或定義欄位的結構或類別的靜態函式之外, readonly 該欄位會被視為值,而不是變數。As stated in Member access, outside an instance constructor or static constructor for a struct or class that defines a readonly field, that field is considered a value, not a variable. 因此,無法取得其位址。As such, its address cannot be taken. 同樣地,無法取得常數的位址。Similarly, the address of a constant cannot be taken.

&運算子不需要明確指派其引數,但在作業之後,會將套用 & 運算子的變數視為明確指派于作業發生所在的執行路徑中。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.

在範例中In the example

using System;

class Test
{
    static void Main() {
        int i;
        unsafe {
            int* p = &i;
            *p = 123;
        }
        Console.WriteLine(i);
    }
}

i 在用來初始化的作業之後,會視為明確指派 &i pi is considered definitely assigned following the &i operation used to initialize p. 生效的指派 *p i 會初始化,但包含此初始化是程式設計師的責任,而且如果已移除指派,就不會發生編譯時期錯誤。The assignment to *p in effect initializes i, but the inclusion of this initialization is the responsibility of the programmer, and no compile-time error would occur if the assignment was removed.

運算子的明確指派規則已 & 存在,因此可以避免區域變數的重複初始化。The rules of definite assignment for the & operator exist such that redundant initialization of local variables can be avoided. 例如,許多外部 Api 都採用 API 所填入之結構的指標。For example, many external APIs take a pointer to a structure which is filled in by the API. 對這類 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

在 unsafe 內容中, ++-- 運算子 (後置 遞增和遞減運算子 以及 前置遞增和遞減運算子) 可以套用至所有類型的指標變數,但除外 void*In an unsafe context, the ++ and -- operators (Postfix increment and decrement operators and Prefix increment and decrement operators) can be applied to pointer variables of all types except void*. 因此,針對每個指標類型 T* ,會隱含地定義下列運算子:Thus, for every pointer type T*, the following operators are implicitly defined:

T* operator ++(T* x);
T* operator --(T* x);

運算子會產生和相同的結果 x + 1 x - 1 ,分別 (指標算術) 。The operators produce the same results as x + 1 and x - 1, respectively (Pointer arithmetic). 換句話說,對於類型的指標變數而言, T* ++ 運算子會新增至變數中所 sizeof(T) 包含的位址,而 -- 運算子會 sizeof(T) 從變數中包含的位址減去。In other words, for a pointer variable of type T*, the ++ operator adds sizeof(T) to the address contained in the variable, and the -- operator subtracts sizeof(T) from the address contained in the variable.

如果指標遞增或遞減運算溢出指標類型的定義域,則結果會是實作為定義,但不會產生任何例外狀況。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

在 unsafe 內容中, + and - 運算子 (加法運算子減法運算子) 可以套用至所有指標類型的值,但除外 void*In an unsafe context, the + and - operators (Addition operator and Subtraction operator) can be applied to values of all pointer types except void*. 因此,針對每個指標類型 T* ,會隱含地定義下列運算子:Thus, for every pointer type T*, the following operators are implicitly defined:

T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);

T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);

T* operator -(T* x, int y);
T* operator -(T* x, uint y);
T* operator -(T* x, long y);
T* operator -(T* x, ulong y);

long operator -(T* x, T* y);

假設有一個 P 指標類型的運算式, T* 以及一個、 N 、或類型的運算式 int uint long ulong ,則運算式 P + NN + P T* 會計算新增 N * sizeof(T) 至指定的位址所產生之類型的指標值 PGiven an expression P of a pointer type T* and an expression N of type int, uint, long, or ulong, the expressions P + N and N + P compute the pointer value of type T* that results from adding N * sizeof(T) to the address given by P. 同樣地,運算式 P - N T* 會計算從指定的位址減去結果之類型的指標值 N * sizeof(T) PLikewise, the expression P - N computes the pointer value of type T* that results from subtracting N * sizeof(T) from the address given by P.

假設有兩個運算式, P 以及 Q 的指標型別 T* ,則運算式 P - Q 會計算由和所指定的位址之間的差異, P Q 然後再除以該差異 sizeof(T)Given two expressions, P and Q, of a pointer type T*, the expression P - Q computes the difference between the addresses given by P and Q and then divides that difference by sizeof(T). 結果的類型一律為 longThe type of the result is always long. 實際上, P - Q 會計算為 ((long)(P) - (long)(Q)) / sizeof(T)In effect, P - Q is computed as ((long)(P) - (long)(Q)) / sizeof(T).

例如:For example:

using System;

class Test
{
    static void Main() {
        unsafe {
            int* values = stackalloc int[20];
            int* p = &values[1];
            int* q = &values[15];
            Console.WriteLine("p - q = {0}", p - q);
            Console.WriteLine("q - p = {0}", q - p);
        }
    }
}

這會產生輸出:which produces the output:

p - q = -14
q - p = 14

如果指標算數運算溢出指標型別的定義域,則會以實所定義的方式截斷結果,但不會產生任何例外狀況。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

在 unsafe 內容中, ==!= 、、 <> <==> 運算子 (關聯式和型別測試運算子) 可以套用至所有指標類型的值。In an unsafe context, the ==, !=, <, >, <=, and => operators (Relational and type-testing operators) can be applied to values of all pointer types. 指標比較運算子如下:The pointer comparison operators are:

bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);

因為從任何指標類型到類型的隱含轉換 void* 都存在,所以可以使用這些運算子來比較任何指標類型的運算元。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.

Sizeof 運算子The sizeof operator

sizeof 運算子會返回指定型別變數所佔用的位元組總數。The sizeof operator returns the number of bytes occupied by a variable of a given type. 指定為運算元的類型 sizeof 必須是) 的 Unmanaged_type (指標類型The type specified as an operand to sizeof must be an unmanaged_type (Pointer types).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

運算子的結果 sizeof 是類型的值 intThe result of the sizeof operator is a value of type int. 針對某些預先定義的類型, sizeof 運算子會產生常數值,如下表所示。For certain predefined types, the sizeof operator yields a constant value as shown in the table below.

運算式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

對於所有其他類型,運算子的結果 sizeof 會是實作為定義,並且分類為值,而不是常數。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.

fixed 陳述式The fixed statement

在不安全的內容中,) 生產的 embedded_statement (語句 會允許額外的結構,也 fixed 就是用來「修正」可移動變數的語句,讓它的位址在語句的持續時間保持不變。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.

fixed_statement
    : 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
    ;

fixed_pointer_declarators
    : fixed_pointer_declarator (','  fixed_pointer_declarator)*
    ;

fixed_pointer_declarator
    : identifier '=' fixed_pointer_initializer
    ;

fixed_pointer_initializer
    : '&' variable_reference
    | expression
    ;

每個 fixed_pointer_declarator 會宣告指定 pointer_type 的區域變數,並使用對應的 fixed_pointer_initializer 所計算的位址來初始化該本機變數。Each fixed_pointer_declarator declares a local variable of the given pointer_type and initializes that local variable with the address computed by the corresponding fixed_pointer_initializer. 在語句中宣告的區域變數 fixed 可在該變數的宣告右邊的任何 fixed_pointer_initializer 中,以及在語句的 embedded_statement 中進行存取 fixedA local variable declared in a fixed statement is accessible in any fixed_pointer_initializer s occurring to the right of that variable's declaration, and in the embedded_statement of the fixed statement. 語句所宣告的本機變數 fixed 會被視為唯讀。A local variable declared by a fixed statement is considered read-only. 如果內嵌語句嘗試透過指派或和) 運算子來修改此區域變數 (++-- 或將它當作或參數傳遞,就會發生編譯時期 ref 錯誤 outA compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the ++ and -- operators) or pass it as a ref or out parameter.

Fixed_pointer_initializer 可以是下列其中一項:A fixed_pointer_initializer can be one of the following:

  • 標記 " & " 後面接著 Variable_reference (精確的規則 ,以決定可移至可移動變數的明確指派) (固定和可移動 變數) 非受控型別 T ,前提是該型別可 T* 隱含轉換成語句中指定的指標型別 fixedThe token "&" followed by a variable_reference (Precise rules for determining definite assignment) to a moveable variable (Fixed and moveable variables) of an unmanaged type T, provided the type T* is implicitly convertible to the pointer type given in the fixed statement. 在此情況下,初始化運算式會計算給定變數的位址,而且在語句的持續時間內,變數會保留為固定位址 fixedIn this case, the initializer computes the address of the given variable, and the variable is guaranteed to remain at a fixed address for the duration of the fixed statement.
  • 具有非受控類型專案之 array_type 的運算式 T ,前提是該類型可 T* 隱含轉換成語句中指定的指標類型 fixedAn expression of an array_type with elements of an unmanaged type T, provided the type T* is implicitly convertible to the pointer type given in the fixed statement. 在此情況下,初始化運算式會計算陣列中第一個元素的位址,而且整個陣列保證會在語句的持續時間內維持固定位址 fixedIn this case, the initializer computes the address of the first element in the array, and the entire array is guaranteed to remain at a fixed address for the duration of the fixed statement. 如果陣列運算式為 null 或陣列有零個元素,則初始化運算式會計算等於零的位址。If the array expression is null or if the array has zero elements, the initializer computes an address equal to zero.
  • 型別為的運算式 string ,前提是型別可 char* 隱含轉換成語句中指定的指標型別 fixedAn expression of type string, provided the type char* is implicitly convertible to the pointer type given in the fixed statement. 在此情況下,初始化運算式會計算字串中第一個字元的位址,而且在語句的持續時間內,整個字串保證會保留在固定位址 fixedIn this case, the initializer computes the address of the first character in the string, and the entire string is guaranteed to remain at a fixed address for the duration of the fixed statement. fixed如果字串運算式為 null,則語句的行為是實作為定義。The behavior of the fixed statement is implementation-defined if the string expression is null.
  • 如果固定大小緩衝區成員的型別可隱含轉換為語句中指定的指標類型,則為參考可移動變數固定大小緩衝區成員的 simple_namemember_access fixedA simple_name or member_access that references a fixed size buffer member of a moveable variable, provided the type of the fixed size buffer member is implicitly convertible to the pointer type given in the fixed statement. 在此情況下,初始化運算式會計算固定大小緩衝區中第一個元素的指標, (運算式中的固定大小緩衝區) ,而且固定大小緩衝區保證會在語句的持續時間內保持固定的位址 fixedIn this case, the initializer computes a pointer to the first element of the fixed size buffer (Fixed size buffers in expressions), and the fixed size buffer is guaranteed to remain at a fixed address for the duration of the fixed statement.

針對 fixed_pointer_initializer 所計算的每個位址, fixed 語句會確保位址所參考的變數不會在語句的持續時間內,由垃圾收集行程重新配置或處置 fixedFor 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. 例如,如果 fixed_pointer_initializer 所計算的位址參考了物件的欄位或陣列實例的專案,則 fixed 語句會保證在語句的存留期期間,不會重新置放或處置包含的物件實例。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.

程式設計人員必須負責確保語句所建立的指標 fixed 不會在執行這些語句之後存留下來。It is the programmer's responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. 例如,當語句所建立的指標 fixed 傳遞至外部 api 時,程式設計人員必須負責確保 api 不會保留這些指標的任何記憶體。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.

範例The example

class Test
{
    static int x;
    int y;

    unsafe static void F(int* p) {
        *p = 1;
    }

    static void Main() {
        Test t = new Test();
        int[] a = new int[10];
        unsafe {
            fixed (int* p = &x) F(p);
            fixed (int* p = &t.y) F(p);
            fixed (int* p = &a[0]) F(p);
            fixed (int* p = a) F(p);
        }
    }
}

示範語句的數種用法 fixeddemonstrates 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. 在每個案例中,使用 regular 運算子會是錯誤, & 因為變數全都分類為可移動變數。In each case it would have been an error to use the regular & operator since the variables are all classified as moveable variables.

上述範例中的第四個 fixed 語句會產生類似第三個的結果。The fourth fixed statement in the example above produces a similar result to the third.

這個語句範例會 fixed 使用 stringThis example of the fixed statement uses string:

class Test
{
    static string name = "xx";

    unsafe static void F(char* p) {
        for (int i = 0; p[i] != '\0'; ++i)
            Console.WriteLine(p[i]);
    }

    static void Main() {
        unsafe {
            fixed (char* p = name) F(p);
            fixed (char* p = "xx") F(p);
        }
    }
}

在一維陣列的 unsafe 內容陣列元素中,會以遞增的索引順序儲存,並以索引開始, 0 並以索引結尾 Length - 1In an unsafe context array elements of single-dimensional arrays are stored in increasing index order, starting with index 0 and ending with index Length - 1. 針對多維陣列,會儲存陣列元素,以便將最右邊維度的索引先增加、下一個左邊的維度,依此類推。For multi-dimensional arrays, array elements are stored such that the indices of the rightmost dimension are increased first, then the next left dimension, and so on to the left. 在取得 fixed 陣列實例指標的語句內 p a ,指標值範圍從 pp + a.Length - 1 表示陣列中元素的位址。Within a fixed statement that obtains a pointer p to an array instance a, the pointer values ranging from p to p + a.Length - 1 represent addresses of the elements in the array. 同樣地,範圍從的 p[0] 變數 p[a.Length - 1] 代表實際的陣列元素。Likewise, the variables ranging from p[0] to p[a.Length - 1] represent the actual array elements. 由於陣列的儲存方式,我們可以處理任何維度的陣列,就好像它是線性一樣。Given the way in which arrays are stored, we can treat an array of any dimension as though it were linear.

例如:For example:

using System;

class Test
{
    static void Main() {
        int[,,] a = new int[2,3,4];
        unsafe {
            fixed (int* p = a) {
                for (int i = 0; i < a.Length; ++i)    // treat as linear
                    p[i] = i;
            }
        }

        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 3; ++j) {
                for (int k = 0; k < 4; ++k)
                    Console.Write("[{0},{1},{2}] = {3,2} ", i, j, k, a[i,j,k]);
                Console.WriteLine();
            }
    }
}

這會產生輸出:which produces the output:

[0,0,0] =  0 [0,0,1] =  1 [0,0,2] =  2 [0,0,3] =  3
[0,1,0] =  4 [0,1,1] =  5 [0,1,2] =  6 [0,1,3] =  7
[0,2,0] =  8 [0,2,1] =  9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

在範例中In the example

class Test
{
    unsafe static void Fill(int* p, int count, int value) {
        for (; count != 0; count--) *p++ = value;
    }

    static void Main() {
        int[] a = new int[100];
        unsafe {
            fixed (int* p = a) Fill(p, 100, -1);
        }
    }
}

fixed語句是用來修正陣列,因此可以將其位址傳遞至採用指標的方法。a fixed statement is used to fix an array so its address can be passed to a method that takes a pointer.

在範例中︰In the example:

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize) {
        int len = s.Length;
        if (len > bufSize) len = bufSize;
        for (int i = 0; i < len; i++) buffer[i] = s[i];
        for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
    }

    Font f;

    unsafe static void Main()
    {
        Test test = new Test();
        test.f.size = 10;
        fixed (char* p = test.f.name) {
            PutString("Times New Roman", p, 32);
        }
    }
}

fixed 語句用來修正結構的固定大小緩衝區,因此它的位址可以用來當做指標。a fixed statement is used to fix a fixed size buffer of a struct so its address can be used as a pointer.

char* 由修正字串實例所產生的值一律會指向以 null 終止的字串。A char* value produced by fixing a string instance always points to a null-terminated string. 在取得字串實例指標的 fixed 語句中 p s ,指標值範圍從 pp + s.Length - 1 表示字串中字元的位址,而指標值 p + s.Length 一律指向 null 字元, (值) 的字元 '\0'Within a fixed statement that obtains a pointer p to a string instance s, the pointer values ranging from p to p + s.Length - 1 represent addresses of the characters in the string, and the pointer value p + s.Length always points to a null character (the character with value '\0').

透過固定指標修改 managed 類型的物件,可能會導致未定義的行為。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.

呼叫需要 "C style" 字串的外部 Api 時,自動 null 終止字串會特別方便。The automatic null-termination of strings is particularly convenient when calling external APIs that expect "C-style" strings. 不過請注意,字串實例允許包含 null 字元。Note, however, that a string instance is permitted to contain null characters. 如果出現這類 null 字元,則會在將字串視為以 null 終止的方式時,截斷字串 char*If such null characters are present, the string will appear truncated when treated as a null-terminated char*.

固定大小的緩衝區Fixed size buffers

固定大小緩衝區可用來將「C 樣式」內嵌陣列宣告為結構的成員,而且主要適用于與非受控 Api 互動。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).

struct_member_declaration_unsafe
    : fixed_size_buffer_declaration
    ;

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type fixed_size_buffer_declarator+ ';'
    ;

fixed_size_buffer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'unsafe'
    ;

buffer_element_type
    : type
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

固定大小的緩衝區宣告可能包含一組屬性 (屬性) 、修飾詞 new (修飾詞) 、四個存取修飾詞的有效組合 (型別參數和條件約束) 和修飾詞 (unsafe Unsafe內容) 。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.

固定大小的緩衝區宣告不允許包含 static 修飾詞。A fixed size buffer declaration is not permitted to include the static modifier.

固定大小緩衝區宣告的緩衝區元素類型會指定由宣告引入的緩衝區 () 的元素類型。The buffer element type of a fixed size buffer declaration specifies the element type of the buffer(s) introduced by the declaration. Buffer 元素類型必須是其中一個預先定義的類型 sbytebyteshortushortintuintlong 、、、、 ulong char float doubleboolThe buffer element type must be one of the predefined types sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or bool.

緩衝區專案類型後面接著固定大小的緩衝區宣告子,每個宣告子都會引進一個新的成員。The buffer element type is followed by a list of fixed size buffer declarators, each of which introduces a new member. 固定大小的緩衝區宣告子是由命名成員的識別碼所組成,後面接著括在和權杖中的常數運算式 [ ]A fixed size buffer declarator consists of an identifier that names the member, followed by a constant expression enclosed in [ and ] tokens. 常數運算式代表該固定大小緩衝區宣告子所引進之成員中的元素數目。The constant expression denotes the number of elements in the member introduced by that fixed size buffer declarator. 常數運算式的類型必須可以隱含轉換成類型 int ,而且值必須是非零的正整數。The type of the constant expression must be implicitly convertible to type int, and the value must be a non-zero positive integer.

固定大小緩衝區的元素保證會在記憶體中依序排列。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

unsafe struct A
{
   public fixed int x[5], y[10], z[100];
}

相當於is equivalent to

unsafe struct A
{
   public fixed int x[5];
   public fixed int y[10];
   public fixed int z[100];
}

運算式中的固定大小緩衝區Fixed size buffers in expressions

固定大小緩衝區成員) 的成員查閱 (運算子 會繼續與欄位的成員查閱相同。Member lookup (Operators) of a fixed size buffer member proceeds exactly like member lookup of a field.

您可以使用 simple_name (型別推斷 ,在運算式中參考固定大小緩衝區) 或是動態多載解析 (的 member_access) 編譯時間檢查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).

將固定大小的緩衝區成員參考為簡單名稱時,效果會與表單的成員存取相同 this.I ,其中 I 是固定大小的緩衝區成員。When a fixed size buffer member is referenced as a simple name, the effect is the same as a member access of the form this.I, where I is the fixed size buffer member.

在表單的成員存取中 E.I ,如果 E 是結構類型,而 I 該結構類型的成員查閱識別固定大小成員,則會評估為下列 E.I 分類:In a member access of the form E.I, if E is of a struct type and a member lookup of I in that struct type identifies a fixed size member, then E.I is evaluated an classified as follows:

  • 如果不 E.I 安全的內容中發生此運算式,就會發生編譯時期錯誤。If the expression E.I does not occur in an unsafe context, a compile-time error occurs.
  • 如果 E 分類為值,就會發生編譯階段錯誤。If E is classified as a value, a compile-time error occurs.
  • 否則,如果 E 是可移動變數 (固定和可移動變數) 而且運算式不是 E.I (Fixed 語句) 的 fixed_pointer_initializer ,就會發生編譯時期錯誤。Otherwise, if E is a moveable variable (Fixed and moveable variables) and the expression E.I is not a fixed_pointer_initializer (The fixed statement), a compile-time error occurs.
  • 否則, E 參考固定變數和運算式的結果,是中固定大小緩衝區成員的第一個元素的指標 I EOtherwise, E references a fixed variable and the result of the expression is a pointer to the first element of the fixed size buffer member I in E. 結果的類型是 S* ,其中是的專案 S 類型 I ,而會分類為值。The result is of type S*, where S is the element type of I, and is classified as a value.

您可以使用第一個元素的指標作業來存取固定大小緩衝區的後續元素。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.

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize) {
        int len = s.Length;
        if (len > bufSize) len = bufSize;
        for (int i = 0; i < len; i++) buffer[i] = s[i];
        for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
    }

    unsafe static void Main()
    {
        Font f;
        f.size = 10;
        PutString("Times New Roman", f.name, 32);
    }
}

明確指派檢查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

在 unsafe 內容中 ,區域變數宣告 (區域變數 宣告) 可能包含從呼叫堆疊配置記憶體的堆疊配置初始化運算式。In an unsafe context, a local variable declaration (Local variable declarations) may include a stack allocation initializer which allocates memory from the call stack.

local_variable_initializer_unsafe
    : stackalloc_initializer
    ;

stackalloc_initializer
    : 'stackalloc' unmanaged_type '[' expression ']'
    ;

Unmanaged_type 指出將儲存在新配置位置的專案類型,而 運算式 表示這些專案的數目。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. 因為堆疊配置的大小不可以是負數,所以會發生編譯時期錯誤,將專案數指定為評估為負數值的 constant_expressionSince 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.

表單的堆疊配置初始化運算式 stackalloc T[E] T 必須是非受控型別, (指標 型別) ,而且 E 必須是型別的運算式 intA stack allocation initializer of the form stackalloc T[E] requires T to be an unmanaged type (Pointer types) and E to be an expression of type int. 此結構會 E * sizeof(T) 從呼叫堆疊配置位元組,並將型別的指標傳回 T* 至新配置的區塊。The construct allocates E * sizeof(T) bytes from the call stack and returns a pointer, of type T*, to the newly allocated block. 如果 E 是負數值,則行為是未定義的。If E is a negative value, then the behavior is undefined. 如果 E 為零,則不會進行任何配置,且傳回的指標是實作為定義。If E is zero, then no allocation is made, and the pointer returned is implementation-defined. 如果沒有足夠的記憶體可配置指定大小的區塊, System.StackOverflowException 就會擲回。If there is not enough memory available to allocate a block of the given size, a System.StackOverflowException is thrown.

新配置記憶體的內容尚未被定義。The content of the newly allocated memory is undefined.

或區塊中不允許堆疊配置初始化運算式 catch finally) (try 語句Stack allocation initializers are not permitted in catch or finally blocks (The try statement).

沒有任何方法可以明確釋放使用配置的記憶體 stackallocThere is no way to explicitly free memory allocated using stackalloc. 當函數成員傳回時,會自動捨棄在執行函式成員時所建立的所有堆疊配置記憶體區塊。All stack allocated memory blocks created during the execution of a function member are automatically discarded when that function member returns. 這會對應至函 alloca 式,這是通常會在 c 和 c + + 中找到的擴充功能。This corresponds to the alloca function, an extension commonly found in C and C++ implementations.

在範例中In the example

using System;

class Test
{
    static string IntToString(int value) {
        int n = value >= 0? value: -value;
        unsafe {
            char* buffer = stackalloc char[16];
            char* p = buffer + 16;
            do {
                *--p = (char)(n % 10 + '0');
                n /= 10;
            } while (n != 0);
            if (value < 0) *--p = '-';
            return new string(p, 0, (int)(buffer + 16 - p));
        }
    }

    static void Main() {
        Console.WriteLine(IntToString(12345));
        Console.WriteLine(IntToString(-999));
    }
}

stackalloc方法中會使用初始化運算式 IntToString ,在堆疊上配置16個字元的緩衝區。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

除了運算子以外 stackalloc ,c # 不提供任何預先定義的結構來管理非垃圾收集的記憶體。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. 例如, Memory 下列類別說明如何從 c # 存取基礎作業系統的堆積函數:For example, the Memory class below illustrates how the heap functions of an underlying operating system might be accessed from C#:

using System;
using System.Runtime.InteropServices;

public static unsafe class Memory
{
    // Handle for the process heap. This handle is used in all calls to the
    // HeapXXX APIs in the methods below.
    private static readonly IntPtr s_heap = GetProcessHeap();

    // Allocates a memory block of the given size. The allocated memory is
    // automatically initialized to zero.
    public static void* Alloc(int size)
    {
        void* result = HeapAlloc(s_heap, HEAP_ZERO_MEMORY, (UIntPtr)size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }

    // Copies count bytes from src to dst. The source and destination
    // blocks are permitted to overlap.
    public static void Copy(void* src, void* dst, int count)
    {
        byte* ps = (byte*)src;
        byte* pd = (byte*)dst;
        if (ps > pd)
        {
            for (; count != 0; count--) *pd++ = *ps++;
        }
        else if (ps < pd)
        {
            for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
        }
    }

    // Frees a memory block.
    public static void Free(void* block)
    {
        if (!HeapFree(s_heap, 0, block)) throw new InvalidOperationException();
    }

    // Re-allocates a memory block. If the reallocation request is for a
    // larger size, the additional region of memory is automatically
    // initialized to zero.
    public static void* ReAlloc(void* block, int size)
    {
        void* result = HeapReAlloc(s_heap, HEAP_ZERO_MEMORY, block, (UIntPtr)size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }

    // Returns the size of a memory block.
    public static int SizeOf(void* block)
    {
        int result = (int)HeapSize(s_heap, 0, block);
        if (result == -1) throw new InvalidOperationException();
        return result;
    }

    // Heap API flags
    private const int HEAP_ZERO_MEMORY = 0x00000008;

    // Heap API functions
    [DllImport("kernel32")]
    private static extern IntPtr GetProcessHeap();

    [DllImport("kernel32")]
    private static extern void* HeapAlloc(IntPtr hHeap, int flags, UIntPtr size);

    [DllImport("kernel32")]
    private static extern bool HeapFree(IntPtr hHeap, int flags, void* block);

    [DllImport("kernel32")]
    private static extern void* HeapReAlloc(IntPtr hHeap, int flags, void* block, UIntPtr size);

    [DllImport("kernel32")]
    private static extern UIntPtr HeapSize(IntPtr hHeap, int flags, void* block);
}

Memory以下提供使用類別的範例:An example that uses the Memory class is given below:

class Test
{
    static unsafe void Main()
    {
        byte* buffer = null;
        try
        {
            const int Size = 256;
            buffer = (byte*)Memory.Alloc(Size);
            for (int i = 0; i < Size; i++) buffer[i] = (byte)i;
            byte[] array = new byte[Size];
            fixed (byte* p = array) Memory.Copy(buffer, p, Size);
            for (int i = 0; i < Size; i++) Console.WriteLine(array[i]);
        }
        finally
        {
            if (buffer != null) Memory.Free(buffer);
        }
    }
}

此範例會透過配置256個位元組的記憶體 Memory.Alloc ,並將值從0增加到255的記憶體區塊初始化。The example allocates 256 bytes of memory through Memory.Alloc and initializes the memory block with values increasing from 0 to 255. 然後,它會配置256元素位元組陣列,並使用 Memory.Copy 將記憶體區塊的內容複寫到位元組陣列。It then allocates a 256 element byte array and uses Memory.Copy to copy the contents of the memory block into the byte array. 最後,會使用釋放記憶體區塊, Memory.Free 而位元組陣列的內容則是在主控台上輸出。Finally, the memory block is freed using Memory.Free and the contents of the byte array are output on the console.