24 Código no seguro

24.1 General

Se requiere una implementación que no admita código no seguro para diagnosticar cualquier uso de las reglas sintácticas definidas en esta cláusula.

El resto de esta cláusula, incluidas todas sus subclases, es condicionalmente normativa.

Nota: El lenguaje principal de C#, tal como se define en las cláusulas anteriores, difiere en particular de C y C++ en su omisión de punteros como un tipo de datos. En su lugar, C# proporciona referencias y la capacidad de crear objetos administrados por un recolector de elementos no utilizados. Este diseño, junto con otras características, hace de C# un lenguaje mucho más seguro que C o C++. En el lenguaje C# principal, simplemente no es posible tener una variable sin inicializar, un puntero "pendiente" o una expresión que indexa una matriz más allá de sus límites. Por lo tanto, se eliminan todas las categorías de errores que plagan de forma rutinaria los programas de C y C++.

Aunque prácticamente todas las construcciones de tipo de puntero en C o C++ tienen un homólogo de tipo de referencia en C#, sin embargo, hay situaciones en las que el acceso a los tipos de puntero se convierte en una necesidad. Por ejemplo, la interacción con el sistema operativo subyacente, el acceso a un dispositivo asignado a memoria o la implementación de un algoritmo crítico para el tiempo podrían no ser posibles o prácticos sin acceso a punteros. Para solucionar esta necesidad, C# proporciona la capacidad de escribir código no seguro.

En el código no seguro, es posible declarar y operar en punteros, realizar conversiones entre punteros de datos y tipos enteros, tomar la dirección de variables y métodos, etc. En cierto sentido, escribir código no seguro es muy parecido a escribir código C en un programa de C#.

De hecho, el código no seguro es una característica "segura" desde la perspectiva de los desarrolladores y los usuarios. El código no seguro se marcará claramente con el modificador unsafe, por lo que los desarrolladores posiblemente no pueden usar características no seguras accidentalmente y el motor de ejecución funciona para asegurarse de que el código no seguro no se puede ejecutar en un entorno que no es de confianza.

nota final

24.2 Contextos no seguros

Las características no seguras de C# solo están disponibles en contextos no seguros. Un contexto no seguro se introduce mediante la inclusión de un unsafe modificador en la declaración de un tipo, miembro o función local, o mediante el empleo de un unsafe_statement:

  • Una declaración de una clase, estructura, interfaz o delegado puede incluir un unsafe modificador, en cuyo caso, toda la extensión textual de esa declaración de tipo (incluido el cuerpo de la clase, la estructura o la interfaz) se considera un contexto no seguro.

    Nota: Si el type_declaration es parcial, solo esa parte es un contexto no seguro. nota final

  • Una declaración de un campo, método, propiedad, evento, indexador, operador, constructor de instancia, finalizador, constructor estático o función local puede incluir un unsafe modificador, en cuyo caso, toda la extensión textual de esa declaración de miembro se considera un contexto no seguro.
  • Un unsafe_statement permite el uso de un contexto no seguro dentro de un bloque. La extensión textual completa del bloque asociado se considera un contexto no seguro. Una función local declarada dentro de un contexto no seguro es no segura.

Las extensiones de gramática asociadas se muestran a continuación y en subclases posteriores.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

Ejemplo: en el código siguiente

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

el unsafe modificador especificado en la declaración de estructura hace que toda la extensión textual de la declaración de estructura se convierta en un contexto no seguro. Por lo tanto, es posible declarar los Left campos y Right de un tipo de puntero. El ejemplo anterior también se podría escribir

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

Aquí, los unsafe modificadores de las declaraciones de campo hacen que esas declaraciones se consideren contextos no seguros.

ejemplo final

Aparte de establecer un contexto no seguro, lo que permite el uso de tipos de puntero, el unsafe modificador no tiene ningún efecto en un tipo o un miembro.

Ejemplo: en el código siguiente

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

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

El modificador no seguro en el F método simplemente A hace que la extensión textual de F se convierta en un contexto no seguro en el que se pueden usar las características no seguras del lenguaje. En la invalidación de F en B, no es necesario volver a especificar el unsafe modificador, a menos que, por supuesto, el F método en B sí mismo necesite acceso a características no seguras.

La situación es ligeramente diferente cuando un tipo de puntero forma parte de la firma del método

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

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

Aquí, dado que Fla firma incluye un tipo de puntero, solo se puede escribir en un contexto no seguro. Sin embargo, el contexto no seguro se puede introducir haciendo que toda la clase no sea segura, como es el caso en A, o mediante la inclusión de un unsafe modificador en la declaración de método, como es el caso en B.

ejemplo final

Cuando el unsafe modificador se usa en una declaración de tipo parcial (§15.2.7), solo esa parte concreta se considera un contexto no seguro.

24.3 Tipos de puntero

24.3.1 General

Un puntero es una variable que es capaz de contener la dirección de una variable o un método estático, denominado destino de ese puntero. Un puntero con valor null es un puntero nulo y actualmente no apunta a una variable ni a un método estático. El acto de intentar acceder al destino de un puntero se denomina desreferenciación (§24.6.2 y §24.6.4).

En un contexto no seguro, un tipo (§8.1) puede ser un pointer_type. Un pointer_type también puede ser el tipo de elemento de una matriz (§17). También se puede usar un pointer_type en una expresión typeof (§12.8.18) fuera de un contexto no seguro (por ejemplo, el uso no es seguro).

pointer_type
    : dataptr_type
    | funcptr_type
    | voidptr_type
    ;

El tipo del destino de un tipo de puntero se denomina tipo de referencia del tipo de puntero. Representa el tipo de la variable a la que apunta un valor del tipo de puntero.

Un pointer_type solo se puede usar en un array_type en un contexto no seguro (§24.2). Un non_array_type es cualquier tipo que no sea un array_type.

A diferencia de las referencias (valores de tipos de referencia), el recolector de elementos no utilizados no realiza un seguimiento de los punteros y los datos o métodos estáticos a los que apuntan. Por este motivo, no se permite que un puntero apunte a una referencia o a una estructura que contenga referencias, y el tipo de referencia de un puntero será un unmanaged_type. Los propios tipos de puntero son tipos no administrados, por lo que se puede usar un tipo de puntero como tipo de referencia para otro tipo de puntero.

La regla intuitiva para mezclar punteros y referencias es que los referentes de referencias (objetos) pueden contener punteros, pero no se permite que los punteros de punteros contengan referencias.

Para una implementación determinada, todos los tipos de puntero tendrán el mismo tamaño y representación. Un valor de puntero nulo se representará mediante todos los bits-cero.

Los tipos de puntero son una categoría independiente de tipos. A diferencia de los tipos de referencia y los tipos de valor, los tipos de puntero no heredan de object y no existen conversiones entre tipos de puntero y object. En concreto, no se admiten conversión boxing y unboxing (§8.3.13) para punteros. Sin embargo, las conversiones se permiten entre diferentes tipos de puntero y entre tipos de puntero y los tipos enteros. Esto se describe en §24.5.

No se puede usar un pointer_type como argumento de tipo (§8.4) y la inferencia de tipos (§12.6.3) produce un error en las llamadas de método genérico que habrían inferido un argumento de tipo para que sea un tipo de puntero.

No se puede usar un pointer_type como un tipo de subexpresión de una operación enlazada dinámicamente (§12.3.3).

No se puede usar un pointer_type como el tipo del primer parámetro en un método de extensión (§15.6.10).

Un pointer_type se puede usar como el tipo de un campo volátil (§15.5.4).

La eliminación dinámica de un tipo E* es el tipo de puntero con el tipo de referencia de la eliminación dinámica de E.

No se puede usar una expresión con un tipo de puntero para proporcionar el valor en un member_declarator dentro de un anonymous_object_creation_expression (§12.8.17.4).

El valor predeterminado (§9.3) para cualquier tipo de puntero es null.

Un método puede devolver un valor de algún tipo y ese tipo puede ser un puntero.

Ejemplo: Cuando se proporciona un puntero a una secuencia contigua de ints, el recuento de elementos de esa secuencia y otro int valor, el método siguiente devuelve la dirección de ese valor en esa secuencia, si se produce una coincidencia; de lo contrario, devuelve 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;
}

ejemplo final

24.3.2 Punteros de datos

Un puntero de datos es un puntero capaz de contener la dirección de una variable que tiene value_type (§8.3.1), funcptr_type (§24.3.3) o voidptr_type (§24.3.4).

dataptr_type
    : value_type ('*')+
    | funcptr_type ('*')+
    | voidptr_type ('*')+
    ;

Un dataptr_type se escribe como un value_type que es un unmanaged_type (§8.8), funcptr_type o voidptr_type, seguido de uno o varios * tokens.

Ejemplo: algunos ejemplos de tipos de puntero de datos se proporcionan en la tabla siguiente:

Ejemplo Descripción
byte* Puntero a byte
int*[] Matriz unidimensional de punteros a int
char** Puntero al puntero a char
delegate*<void>* Puntero a un puntero a un método estático sin parámetros y un void tipo de valor devuelto
void** Puntero al puntero al tipo desconocido

ejemplo final

Nota: A diferencia de C y C++, cuando se declaran varios punteros en la misma declaración, en C# solo * se escribe junto con el tipo subyacente, no como un signo de puntuación de prefijo en cada nombre de puntero. Por ejemplo:

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

nota final

El valor distinto de null de un puntero de datos que tiene el tipo T* representa la dirección de una variable de tipo T. El operador * de direccionamiento indirecto del puntero (§24.6.2) se puede usar para acceder a esta variable.

Ejemplo: dada una variable P de tipo int*, la expresión *P denota la int variable que se encuentra en la dirección contenida en P. ejemplo final

Nota: Aunque los punteros se pueden pasar como parámetros por referencia, si lo hace con punteros de datos, puede provocar un comportamiento indefinido, ya que el puntero podría estar bien establecido para apuntar a una variable local que ya no existe cuando el método llamado devuelve o el objeto fijo al que se usó para apuntar ya no se ha corregido. Por ejemplo:

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);
            // Undefined behavior
            // Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}");
        }
    }
}

nota final

En un contexto no seguro, hay varias construcciones disponibles para funcionar en punteros de datos:

  • El operador unario * se puede usar para realizar la direccionamiento indirecto del puntero (§24.6.2).
  • El -> operador se puede usar para acceder a un miembro de una estructura a través de un puntero (§24.6.3).
  • El [] operador se puede usar para indizar un puntero (§24.6.4).
  • El operador unario & se puede usar para obtener la dirección de una variable (§24.6.5).
  • Los ++ operadores y -- se pueden usar para incrementar y disminuir punteros (§24.6.6).
  • Los operadores binarios + y - se pueden usar para realizar la aritmética de puntero (§24.6.7).
  • Los ==operadores , !=, <, >, <=y >= se pueden usar para comparar punteros (§24.6.8).
  • El stackalloc operador se puede usar para asignar memoria desde la pila de llamadas (§24.9).
  • La fixed instrucción se puede usar para corregir temporalmente una variable para que se pueda obtener su dirección (§24.7).

Punteros de función 24.3.3

Un puntero de función es un puntero capaz de contener la dirección de un método estático.

funcptr_type
    : 'delegate' '*' calling_convention_specifier? 
      '<' funcptr_parameter_list funcptr_return_type '>'
    ;

calling_convention_specifier
    : 'managed'
    | 'unmanaged' ('[' unmanaged_calling_convention ']')?
    ;

unmanaged_calling_convention
    : 'Cdecl'
    | 'Stdcall'
    | 'Thiscall'
    | 'Fastcall'
    | identifier (',' identifier)*
    ;

funcptr_parameter_list
    : (funcptr_parameter ',')*
    ;

funcptr_parameter
    : parameter_mode_modifier? type
    ;

funcptr_return_type
    : ref_kind? return_type
    ;

Al igual que un método tiene una firma (§7.6), un tipo de puntero de función tiene una firma para el tipo de método al que puede apuntar. Esa firma incluye la convención de llamada.

El valor no NULL de un puntero de función que tiene el tipo T representa la dirección de un método que tiene una firma compatible con el tipo T.

Si no se proporciona ningún calling_convention_specifier , el valor predeterminado es managed, lo que da lugar a que se use el mecanismo predeterminado del entorno de ejecución. Se pueden especificar convenciones no administradas específicas mediante unmanaged_calling_convention cuyos tokens se asignan a nombres definidos por la implementación que tienen semántica definida por la implementación. El conjunto de combinaciones válidas de estos tokens está definido por la implementación.

Nota: El calling_convention_specifier permite elegir un mecanismo de llamada potencialmente más eficaz o para los métodos escritos en lenguajes distintos de C# a los que se llamará. nota final.

Ejemplo: algunos ejemplos de tipos de puntero de función se proporcionan en la tabla siguiente:

Ejemplo Descripción
delegate*<void> Puntero a un método administrado sin parámetros y un void tipo de valor devuelto
delegate*<void>[] Matriz de punteros a un método administrado sin parámetros y un void tipo de valor devuelto
delegate*<string, string, bool> Puntero a un método administrado que tiene dos string parámetros y un bool tipo de valor devuelto
delegate*<ref readonly int> Puntero a un método administrado que no tiene parámetros y devuelve un ref readonly int
delegate*<delegate*<int>, void> Puntero a un método administrado que tiene un parámetro que es un puntero a un método que no tiene parámetros y un int tipo de valor devuelto, y un void tipo de valor devuelto
delegate* unmanaged[Stdcall]<void> Puntero a un método no administrado sin parámetros y un void tipo de valor devuelto, mediante la convención de Stdcall llamada

Tenga en cuenta lo siguiente.

unsafe class Util
{
    static void Log() { ... }
    static void Log(string p1) { ... }

    static void User()
    {
        delegate*<void>[] ary1 = new delegate*<void>[] { &Log, null };
        foreach (var element in ary1)
        {
            if (element != null)
            {
                element();     // call the method being pointed to
            }
        }
    }
}

Dado que los punteros de función del punto de matriz a métodos sin parámetros, &Log toma la dirección del Log método sin parámetros. ejemplo final

unmanaged_calling_convention admite un pequeño número de convenciones predefinidas (Cdecl, Stdcall, Thiscally Fastcall, todas ellas palabras clave contextuales), que se pueden usar de forma independiente o como identificador en una lista de identificadoresde unmanaged_calling_convention . Se permiten otras convenciones definidas por la implementación y se pueden combinar varias convenciones mediante una lista de identificadores , posiblemente que contenga una o varias de estas convenciones predefinidas. La búsqueda y el procesamiento de identificadores de esta lista se realizan de forma definida por la implementación.

Ejemplo: Dada una convención SuppressGCTransitionde llamada definida por la implementación ,

unsafe class C
{
    delegate* unmanaged[SuppressGCTransition]<int, int> fpx;
    delegate* unmanaged[Stdcall, SuppressGCTransition]<int, int> fpy;
}

ambos casos usan la regla de gramática identifier-list. ejemplo final

Los atributos personalizados no se pueden aplicar a un funcptr_type ni a ninguno de sus elementos.

Un parámetro de tipo funcptr_type no se marcará como params (§15.6.2.1).

En un contexto no seguro, las siguientes construcciones están disponibles para funcionar en punteros de función:

  • El & operador se puede usar para obtener la dirección de un método estático (§24.6.5)
  • Los ==operadores , !=, <, >, <=y => se pueden usar para comparar punteros (§24.6.8).
  • El operador invocation_expression, (), se puede usar para llamar al método al que se apunta (§12.8.9.1).

24.3.4 Punteros void

Un puntero void es un puntero capaz de contener el valor de un puntero de datos o un puntero de función.

voidptr_type
    : 'void' '*'
    ;

Un voidptr_type se escribe como la palabra clave void seguida de tu * token.

Ejemplo: algunos ejemplos de tipos de puntero void se proporcionan en la tabla siguiente:

Ejemplo Descripción
void* Puntero al tipo desconocido
void*[,,] Matriz tridimensional de punteros a tipo desconocido

ejemplo final

Un voidptr_type representa un puntero a un tipo desconocido. Dado que se desconoce el tipo de referencia, el operador de direccionamiento indirecto no se puede aplicar a un puntero de tipo void*, ni se puede realizar ninguna aritmética en dicho puntero. Sin embargo, un puntero de tipo void* se puede convertir a cualquier tipo de puntero (y viceversa) y en comparación con los valores de otros tipos de puntero (§24.6.8).

24.4 Variables fijas y movibles

El operador address-of (§24.6.5) y la fixed instrucción (§24.7) dividen las variables en dos categorías: variables fijasy variables desplazables.

Las variables fijas residen en ubicaciones de almacenamiento que no se ven afectadas por el funcionamiento del recolector de elementos no utilizados. (Algunos ejemplos de variables fijas incluyen variables locales, parámetros de valor y variables creadas por punteros de datos de desreferenciación). Por otro lado, las variables movibles residen en ubicaciones de almacenamiento que están sujetas a reubicación o eliminación por parte del recolector de elementos no utilizados. (Algunos ejemplos de variables desplazables incluyen campos en objetos y elementos de matrices).

El & operador (§24.6.5) permite obtener la dirección de una variable fija sin restricciones. Sin embargo, dado que una variable desplazable está sujeta a reubicación o eliminación por parte del recolector de elementos no utilizados, la dirección de una variable desplazable solo se puede obtener mediante un fixed statement (§24.7) y esa dirección permanece válida solo durante la duración de esa fixed instrucción.

En términos precisos, una variable fija es una de las siguientes:

  • Variable resultante de un simple_name (§12.8.4) que hace referencia a una variable local, un parámetro de valor o una matriz de parámetros, a menos que unastatic función no anónima capture la variable (§12.22.6.2).
  • Variable resultante de un member_access (§12.8.7
  • Variable resultante de un pointer_indirection_expression (§24.6.2) del formulario *P, un pointer_member_access (§24.6.3) del formulario P->Io un pointer_element_access (§24.6.4) del formulario P[E].

Todas las demás variables se clasifican como variables desplazables.

Un campo estático se clasifica como una variable desplazable. Además, un parámetro por referencia se clasifica como una variable desplazable, incluso si el argumento proporcionado para el parámetro es una variable fija. Por último, una variable generada por la desreferenciación de un puntero de datos siempre se clasifica como una variable fija.

24.5 Conversiones de puntero

24.5.1 General

En un contexto no seguro, el conjunto de conversiones implícitas disponibles (§10.2) se extiende para incluir las siguientes conversiones implícitas de puntero:

  • De cualquier pointer_type al tipo void*.
  • Desde null_literal (§6.4.5.7) a cualquier pointer_type.
  • De funcptr_typeF0 a funcptr_typeF1, siempre que se cumpla lo siguiente:
    • F0 y F1 tienen el mismo número de parámetros, y cada parámetro D0n de F0 tiene los mismos modificadores de parámetro por referencia que el parámetro D1n correspondiente en F1.
    • Para cada parámetro de valor, existe una conversión de identidad, conversión de referencia implícita o conversión de puntero implícita del tipo de parámetro en F0 al tipo de parámetro correspondiente en F1.
    • Para cada parámetro por referencia, el tipo de parámetro de F0 es el mismo que el tipo de parámetro correspondiente en F1.
    • Si el tipo de valor devuelto es por valor, existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo F1 de valor devuelto al tipo de valor devuelto de F0.
    • Si el tipo de valor devuelto es por referencia, el tipo de valor devuelto y ref los modificadores de F1 son los mismos que el tipo de valor devuelto y ref los modificadores de F0.
    • La convención de llamada de F0 es la misma que la convención de llamada de F1.

Además, en un contexto no seguro, el conjunto de conversiones explícitas disponibles (§10.3) se extiende para incluir las siguientes conversiones de puntero explícitas:

  • Desde cualquier pointer_type a cualquier otro pointer_type.
  • De sbyte, byte, short, ushort, int, uint, nint, nuint, , longo ulong a cualquier pointer_type.
  • Desde cualquier pointer_type a sbyte, byte, , intnintuintshortushort, nuint, , , longo .ulong

Por último, en un contexto no seguro, el conjunto de conversiones implícitas estándar (§10.4.2) incluye las siguientes conversiones de puntero:

  • De cualquier pointer_type al tipo void*.
  • De null_literal a cualquier pointer_type.

Las conversiones entre dos tipos de puntero nunca cambian el valor real del puntero. En otras palabras, una conversión de un tipo de puntero a otro no tiene ningún efecto en la dirección subyacente dada por el puntero.

Cuando se convierte un tipo de puntero en un dataptr_type, si el puntero resultante no está alineado correctamente para el tipo de puntero, el comportamiento no se define si el resultado se desreferencia. En general, el concepto "alineado correctamente" es transitivo: si un puntero al tipo A está alineado correctamente para un puntero al tipo B, que, a su vez, se alinea correctamente para un puntero al tipo C, un puntero al tipo se alinea correctamente para un puntero Aal tipo C .

Ejemplo: considere el siguiente caso en el que se tiene acceso a una variable que tiene un tipo a través de un puntero a otro tipo:

unsafe static void M()
{
    char c = 'A';
    char* pc = &c;
    void* pv = pc;
    int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int
    int i = *pi;        // read 32-bit int; undefined
    *pi = 123456;       // write 32-bit int; undefined
}

ejemplo final

Cuando un tipo de puntero se convierte en un puntero a byte, el resultado apunta a la dirección byte más baja de la variable. Los incrementos sucesivos del resultado, hasta el tamaño de la variable, producen punteros a los bytes restantes de esa variable.

Ejemplo: el método siguiente muestra cada uno de los ocho bytes de un double como un valor hexadecimal:

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

Por supuesto, la salida producida depende de la endianidad. Una posibilidad es " BA FF 51 A2 90 6C 24 45".

ejemplo final

Las asignaciones entre punteros y enteros están definidas por la implementación.

Nota: Sin embargo, en arquitecturas de CPU de 32 y 64 bits con un espacio de direcciones lineal, las conversiones de punteros a tipos enteros o desde ellos normalmente se comportan exactamente como conversiones de uint valores o ulong , respectivamente, hacia o desde esos tipos enteros. nota final

24.5.2 Matrices de punteros

Las matrices de punteros se pueden construir mediante array_creation_expression (§12.8.17.5) en un contexto no seguro. Solo se permiten algunas de las conversiones que se aplican a otros tipos de matriz en matrices de punteros:

  • La conversión de referencia implícita (§10.2.8) de cualquier array_type a System.Array y las interfaces que implementa también se aplica a las matrices de punteros. Sin embargo, cualquier intento de acceder a los elementos de matriz a través System.Array de o las interfaces que implementa puede dar lugar a una excepción en tiempo de ejecución, ya que los tipos de puntero no se pueden convertir en object.
  • Las conversiones de referencia implícitas y explícitas (§10.2.8, §10.3.5) de un tipo S[] de matriz unidimensional a System.Collections.Generic.IList<T> y sus interfaces base genéricas nunca se aplican a las matrices de punteros.
  • La conversión de referencia explícita (§10.3.5) de System.Array y las interfaces que implementa en cualquier array_type se aplica a las matrices de punteros.
  • Las conversiones de referencia explícitas (§10.3.5) de System.Collections.Generic.IList<S> y sus interfaces base a un tipo T[] de matriz unidimensional nunca se aplican a matrices de punteros, ya que los tipos de puntero no se pueden usar como argumentos de tipo y no hay conversiones de tipos de puntero a tipos que no son de puntero.

Estas restricciones significan que la expansión de la foreach instrucción sobre matrices descritas en §9.4.4.17 no se puede aplicar a matrices de punteros. En su lugar, una foreach instrucción del formulario

foreach (V v in x) embedded_statement

donde el tipo de es un tipo de x matriz de la forma T[,,...,], n es el número de dimensiones menos 1 y T o V es un tipo de puntero, se expande mediante bucles for anidados como se indica a continuación:

{
    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[i0,i1,...,in];
                *embedded_statement*
            }
        }
    }
}

Las variables a, i0, i1, ... in no son visibles ni accesibles para x ni para el embedded_statement ni para ningún otro código fuente del programa. La variable v es de solo lectura en la instrucción insertada. Si no hay una conversión explícita (§24.5) de T (el tipo de elemento) a V, se produce un error y no se realizan pasos adicionales. Si x tiene el valor null, se produce una System.NullReferenceException excepción en tiempo de ejecución.

Nota: Aunque los tipos de puntero no se permiten como argumentos de tipo, las matrices de punteros se pueden usar como argumentos de tipo. nota final

24.6 Punteros en expresiones

24.6.1 General

En un contexto no seguro, una expresión puede producir un resultado de un tipo de puntero, pero fuera de un contexto no seguro, es un error en tiempo de compilación para que una expresión sea de un tipo de puntero. En términos precisos, fuera de un contexto no seguro, se produce un error en tiempo de compilación si hay algún simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) o element_access (§12.8.12) es de un tipo de puntero.

En un contexto no seguro, las primary_expression (§12.8) y unary_expression (§12.9) permiten construcciones adicionales, que se describen en las subcláusulas siguientes.

Nota: La gramática implica la precedencia y la asociatividad de los operadores no seguros. nota final

Todos los aspectos de la inferencia de tipos con respecto a los punteros de función se describen en las subclases correspondientes de §12.6 y §12.8.

24.6.2 Direccionamiento indirecto de puntero

Un pointer_indirection_expression consta de un asterisco (*) seguido de un unary_expression.

pointer_indirection_expression
    : '*' unary_expression
    ;

El operador unario * denota la direccionamiento indirecto del puntero y se usa para obtener la variable a la que apunta un puntero de datos. El resultado de evaluar *P, donde P es una expresión de un tipo T*de puntero , es una variable de tipo T. Se trata de un error en tiempo de compilación para aplicar el operador unario * a un operando que tiene el tipo funcptr_type o voidptr_type.

Nota: En C/C++, se puede desreferenciar un puntero de función para obtener en la función subyacente para llamarla, como en (*fp)(). Esta desreferencia explícita no está permitida en C#. nota final

El efecto de aplicar el operador unario * a un puntero de datos NULL está definido por la implementación. En concreto, no hay ninguna garantía de que esta operación produzca una System.NullReferenceExceptionexcepción .

Si se ha asignado un valor no válido al puntero de datos, el comportamiento del operador unario * no está definido.

Nota: Entre los valores no válidos para desreferenciar un puntero de datos por parte del operador unario * , hay una dirección alineada incorrectamente para el tipo al que apunta (vea el ejemplo de §24.5) y la dirección de una variable después del final de su vigencia.

Para fines del análisis de asignación definitiva, una variable generada mediante la evaluación de una expresión del formulario *P se considera asignada inicialmente (§9.4.2).

Acceso a miembros del puntero 24.6.3

Un pointer_member_access consta de un primary_expression, seguido de un token "->", seguido de un identificador y un type_argument_list opcional.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

En un acceso de miembro de puntero del formulario P->I, P debe ser una expresión de un tipo de puntero de datos y I denotará un miembro accesible del tipo al que P apunta. Es un error en tiempo de compilación para P que el tipo funcptr_type o voidptr_type.

Un acceso de miembro de puntero del formulario P->I se evalúa exactamente como (*P).I. Para obtener una descripción del operador de direccionamiento indirecto de puntero (*), consulte §24.6.2. Para obtener una descripción del operador de acceso a miembros (.), consulte §12.8.7.

Ejemplo: en el código siguiente

struct Point
{
    public int x;
    public int y;
    public override string ToString() => $"({x},{y})";
}

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

El -> operador se usa para acceder a los campos e invocar un método de una estructura a través de un puntero. Dado que la operación P->I es exactamente equivalente a (*P).I, el Main método podría haber sido igualmente escrito:

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

ejemplo final

Acceso al elemento puntero 24.6.4

Un pointer_element_access consta de un primary_expression seguido de una expresión entre "[" y "]".

pointer_element_access
    : primary_expression '[' expression ']'
    ;

Al reconocer un primary_expression si las alternativas element_access y pointer_element_access (§24.6.4) son aplicables, se elegirá esta última si el primary_expression incrustado es de tipo de puntero (§24.3).

En un acceso de elemento de puntero del formato P[E], P debe ser una expresión de un tipo de puntero distinto void*de , y E será una expresión que se puede convertir implícitamente en int, uint, nint, , nuint, longo ulong.

El acceso a un elemento de puntero del formulario P[E] se evalúa exactamente como *(P + E). Para obtener una descripción del operador de direccionamiento indirecto de puntero (*), consulte §24.6.2. Para obtener una descripción del operador de suma de puntero (+), consulte §24.6.7.

Ejemplo: en el código siguiente

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

Se usa un acceso de elemento de puntero para inicializar el búfer de caracteres en un for bucle. Dado que la operación P[E] es exactamente equivalente a *(P + E), el ejemplo podría haber sido igualmente escrito:

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

ejemplo final

El operador de acceso al elemento de puntero no comprueba si hay errores no enlazados y el comportamiento al acceder a un elemento fuera de límite no está definido.

Nota: Esto es lo mismo que C y C++. nota final

24.6.5 El operador address-of

Un addressof_expression consta de una y comercial (&) seguida de un unary_expression.

addressof_expression
    : '&' unary_expression
    ;

unary_expression designarán una variable o un grupo de métodos. El caso de variable se describe inmediatamente a continuación.

Dada una expresión E que es de un tipo T y se clasifica como una variable fija (§24.4), la construcción &E calcula la dirección de la variable dada por E. El tipo del resultado es T* y se clasifica como un valor. Se produce un error en tiempo de compilación si E no se clasifica como una variable, si E se clasifica como una variable local de solo lectura o si E denota una variable desplazable. En el último caso, se puede usar una instrucción fija (§24.7) para "corregir" temporalmente la variable antes de obtener su dirección.

Nota: Como se indica en §12.8.7, fuera de un constructor de instancia o constructor estático para una estructura o clase que define un readonly campo, ese campo se considera un valor, no una variable. Por lo tanto, no se puede tomar su dirección. Del mismo modo, no se puede tomar la dirección de una constante. nota final

El & operador no requiere que su operando se asigne definitivamente, pero después de una & operación, la variable a la que se aplica el operador se considera definitivamente asignada en la ruta de acceso de ejecución en la que se produce la operación. Es responsabilidad del programador asegurarse de que la inicialización correcta de la variable realmente tiene lugar en esta situación.

Ejemplo: en el código siguiente

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

i se considera definitivamente asignado después de la &i operación utilizada para inicializar p. La asignación a *p en vigor inicializa i, pero la inclusión de esta inicialización es responsabilidad del programador y no se produciría ningún error en tiempo de compilación si se quitase la asignación.

ejemplo final

Nota: Las reglas de asignación definitiva para el & operador existen de modo que se pueda evitar la inicialización redundante de variables locales. Por ejemplo, muchas API externas toman un puntero a una estructura rellenada por la API. Las llamadas a estas API normalmente pasan la dirección de una variable de estructura local y, sin la regla, se requeriría la inicialización redundante de la variable de estructura. nota final

Nota: Cuando una función anónima captura una variable local, un parámetro de valor o una matriz de parámetros (§12.8.24), esa variable local, parámetro o matriz de parámetros ya no se considera una variable fija (§24.7), pero en su lugar se considera una variable desplazable. Por lo tanto, es un error para que cualquier código no seguro tome la dirección de una variable local, un parámetro de valor o una matriz de parámetros capturada por una función anónima. nota final

El caso de unary_expression designar un grupo de métodos se describe inmediatamente a continuación.

En un contexto no seguro, un método M es compatible con un funcptr_typeF si se cumplen todas las siguientes condiciones:

  • M y F tienen el mismo número de parámetros y cada parámetro de M tiene los mismos modificadores ref, outo in como parámetro correspondiente en F.
  • Para cada parámetro de valor, existe una conversión de identidad, conversión de referencia implícita o conversión de puntero implícita del tipo de parámetro en M al tipo de parámetro correspondiente en F.
  • Para cada parámetro por referencia, el tipo de parámetro de M es el mismo que el tipo de parámetro correspondiente en F.
  • Si el tipo de valor devuelto es por valor, existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo F de valor devuelto al tipo de valor devuelto de M.
  • Si el tipo de valor devuelto es por referencia, el tipo de valor devuelto y ref los modificadores de F son los mismos que el tipo de valor devuelto y ref los modificadores de M.
  • La convención de llamada de M es la misma que la convención de llamada de F.
  • M es un método estático.

Existe una conversión implícita desde un unary_expression cuyo destino es un grupo Ede métodos , a un tipo F de puntero de función compatible si E contiene al menos un método que se aplica en su forma normal a una lista de argumentos construida mediante el uso de los tipos de parámetros y modificadores de F, como se describe en lo siguiente:

  • Se selecciona un único método M correspondiente a una invocación de método del formulario E(A) con las siguientes modificaciones:
    • La lista A de argumentos es una lista de expresiones, cada una clasificada como una variable y con el tipo y modificador del funcptr_parameter_list correspondiente de F.
    • Los métodos candidatos son solo los métodos que son aplicables en su forma normal, no los aplicables en su forma expandida.
    • Los métodos candidatos son solo los métodos estáticos.
  • Si el algoritmo de resolución de sobrecarga genera un error, se produce un error en tiempo de compilación. De lo contrario, el algoritmo genera un único método mejor M que tiene el mismo número de parámetros que F y se considera que la conversión existe.
  • El método M seleccionado será compatible (tal como se definió anteriormente) con el tipo Fde puntero de función . De lo contrario, se produce un error en tiempo de compilación.
  • El resultado de la conversión es un puntero de función de tipo F.

24.6.6 Incremento y disminución del puntero

En un contexto no seguro, los ++ operadores y -- (§12.8.16 y §12.9.7) se pueden aplicar a variables de puntero de todos los tipos Es un error en tiempo de compilación para que estos operadores se apliquen a variables de tipo funcptr_type o voidptr_type. Por lo tanto, para cada tipo T*de puntero de datos , los operadores siguientes se definen implícitamente:

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

Los operadores producen los mismos resultados que x+1 y x-1, respectivamente (§24.6.7). Es decir, para una variable de puntero de datos de tipo T*, el ++ operador agrega sizeof(T) a la dirección contenida en la variable y el -- operador resta sizeof(T) de la dirección contenida en la variable.

Si una operación de incremento o disminución de puntero desborda el dominio del tipo de puntero, el resultado es definido por la implementación y no se requiere ninguna excepción.

24.6.7: aritmética de puntero

En un contexto no seguro, el operador (§12.13.5) y - el + operador (§12.13.6) se pueden aplicar a los valores de todos los tipos de puntero de datos. Se trata de un error en tiempo de compilación para que estos operadores se apliquen a un valor de tipo funcptr_type o voidptr_type. Por lo tanto, para cada tipo T*de puntero , se definen implícitamente los operadores siguientes:

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);

No hay operadores predefinidos para desplazamientos de suma o resta de punteros con desplazamientos de entero nativo (§8.3.6). En su lugar, nint los valores y nuint se promoverán a long y ulong, respectivamente, con aritmética de puntero mediante los operadores predefinidos para esos tipos.

Dada una expresión P de un tipo T* de puntero de datos y una expresión N de tipo int, uint, longo ulong, las expresiones P + N y N + P calculan el valor de puntero de tipo T* que resulta de agregar N * sizeof(T) a la dirección dada por P. Del mismo modo, la expresión P – N calcula el valor de puntero de tipo T* que resulta de restar N * sizeof(T) de la dirección dada por P.

Dadas dos expresiones, y Q, de un tipo T*de puntero de datos , la expresión P – Q calcula la diferencia entre las direcciones dadas por P yQ, a continuación, P divide esa diferencia por sizeof(T). El tipo del resultado siempre longes . En efecto, P - Q se calcula como ((long)(P) - (long)(Q)) / sizeof(T).

Ejemplo:

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

que genera la salida:

p - q = -14
q - p = 14

ejemplo final

Si una operación aritmética de puntero desborda el dominio del tipo de puntero, el resultado se trunca de forma definida por la implementación y no se requiere ninguna excepción.

Comparación de punteros 24.6.8

En un contexto no seguro, los ==operadores , !=, <>, , <=y >= (§12.15) se pueden aplicar de forma segura a los valores de todos los dataptr_types y a los valores de todos los voidptr_types que son copias de dataptr_type valores. Los operadores de comparación de punteros son:

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);

Dado que existe una conversión implícita desde cualquier tipo de puntero al void* tipo, los operandos de cualquier tipo de puntero se pueden comparar mediante estos operadores. Los operadores de comparación comparan las direcciones dadas por los dos operandos como si fueran enteros sin signo. Sin embargo, el comportamiento al comparar valores de funcptr_types, o void* copias de los mismos, no está definido.

Nota: En algunas plataformas, es posible que cuando la dirección de un método determinado se tome varias veces, los resultados difieren y hacen comparaciones con ellos no confiables. nota final

24.6.9 Operador sizeof

Para determinados tipos predefinidos (§12.8.19), el sizeof operador produce un valor constante int . Para todos los demás tipos, el resultado del sizeof operador está definido por la implementación y se clasifica como un valor, no como una constante.

El orden en el que los miembros se empaquetan en un struct no se especifican.

Con fines de alineación, puede haber relleno sin nombre al principio de una estructura, dentro de una estructura y al final de la estructura. El contenido de los bits usados como relleno es indeterminado.

Cuando se aplica a un operando que tiene un tipo de estructura, el resultado es el número total de bytes de una variable de ese tipo, incluido cualquier relleno.

24.7 La instrucción fija

En un contexto no seguro, la producción de embedded_statement (§13.1) permite una construcción adicional, la instrucción fija, que se usa para "corregir" una variable desplazable de modo que su dirección permanezca constante durante la duración de la instrucción.

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
    ;

Cada fixed_pointer_declarator declara una variable local del pointer_type especificado e inicializa esa variable local con la dirección calculada por el fixed_pointer_initializer correspondiente. pointer_type no se funcptr_type. Se puede acceder a una variable local declarada en una instrucción fija en cualquier fixed_pointer_initializerque se produzca a la derecha de la declaración de esa variable y en la embedded_statement de la instrucción fija. Una variable local declarada por una instrucción fija se considera de solo lectura. Se produce un error en tiempo de compilación si la instrucción insertada intenta modificar esta variable local (a través de la asignación o los ++ operadores y -- ) o pasarla como parámetro de referencia o salida.

Es un error usar una variable local capturada (§12.22.6.2), un parámetro de valor o una matriz de parámetros en un fixed_pointer_initializer. Un fixed_pointer_initializer puede ser uno de los siguientes:

  • El token "&" seguido de un variable_reference (§9.5) a una variable moveable (§24.4) de un tipo Tno administrado, siempre que el tipo T* se pueda convertir implícitamente al tipo de puntero especificado en la fixed instrucción . En este caso, el inicializador calcula la dirección de la variable especificada y se garantiza que la variable permanezca en una dirección fija mientras dure la instrucción fija.
  • Expresión de un array_type con elementos de un tipo Tno administrado , siempre que el tipo T* se puede convertir implícitamente al tipo de puntero especificado en la instrucción fija. En este caso, el inicializador calcula la dirección del primer elemento de la matriz y se garantiza que toda la matriz permanezca en una dirección fija durante la duración de la fixed instrucción. Si la expresión de matriz es null o si la matriz tiene cero elementos, el inicializador calcula una dirección igual a cero.
  • Expresión de tipo string, siempre que el tipo char* se puede convertir implícitamente al tipo de puntero especificado en la fixed instrucción . En este caso, el inicializador calcula la dirección del primer carácter de la cadena y se garantiza que toda la cadena permanezca en una dirección fija mientras dure la fixed instrucción. El comportamiento de la instrucción está definido por la fixed implementación si la expresión de cadena es null.
  • Una expresión de tipo que no sea array_type o string, siempre que exista un método accesible o un método de extensión accesible que coincida con la firma ref [readonly] T GetPinnableReference(), donde T es un unmanaged_type y T* se puede convertir implícitamente al tipo de puntero especificado en la fixed instrucción . En este caso, el inicializador calcula la dirección de la variable devuelta y esa variable se garantiza que permanezca en una dirección fija mientras dure la fixed instrucción. La instrucción puede usar un GetPinnableReference() método cuando la fixed resolución de sobrecarga (§12.6.4) genera exactamente un miembro de función y ese miembro de función satisface las condiciones anteriores. El GetPinnableReference método debe devolver una referencia a una dirección igual a cero, como la que se devuelve cuando System.Runtime.CompilerServices.Unsafe.NullRef<T>() no hay datos que anclar.
  • Un simple_name o member_access que hace referencia a un miembro de búfer de tamaño fijo de una variable desplazable, siempre que el tipo del miembro de búfer de tamaño fijo se pueda convertir implícitamente al tipo de puntero especificado en la fixed instrucción . En este caso, el inicializador calcula un puntero al primer elemento del búfer de tamaño fijo (§24.8.3) y se garantiza que el búfer de tamaño fijo permanezca en una dirección fija mientras dure la fixed instrucción.

Para cada dirección calculada por un fixed_pointer_initializer la fixed instrucción garantiza que la variable a la que hace referencia la dirección no esté sujeta a reubicación o eliminación por parte del recolector de elementos no utilizados durante la duración de la fixed instrucción.

Ejemplo: si la dirección calculada por un fixed_pointer_initializer hace referencia a un campo de un objeto o a un elemento de una instancia de matriz, la instrucción fija garantiza que la instancia de objeto contenedor no se reubica ni elimina durante la vigencia de la instrucción. ejemplo final

Es responsabilidad del programador asegurarse de que los punteros creados por instrucciones fijas no sobreviven más allá de la ejecución de esas instrucciones.

Ejemplo: cuando los punteros creados por fixed instrucciones se pasan a api externas, es responsabilidad del programador asegurarse de que las API no conservan memoria de estos punteros. ejemplo final

Los objetos fijos pueden provocar la fragmentación del montón (porque no se pueden mover). Por ese motivo, los objetos solo deben corregirse cuando sea absolutamente necesario y, a continuación, solo durante la menor cantidad de tiempo posible.

Ejemplo: El ejemplo

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);
        }
    }
}

muestra varios usos de la fixed instrucción . La primera instrucción corrige y obtiene la dirección de un campo estático, la segunda instrucción corrige y obtiene la dirección de un campo de instancia, y la tercera instrucción corrige y obtiene la dirección de un elemento de matriz. En cada caso, habría sido un error usar el operador normal & , ya que todas las variables se clasifican como variables movibles.

Las instrucciones tercera y cuarta fixed del ejemplo anterior generan resultados idénticos. En general, para una instancia ade matriz , especificar a[0] en una fixed instrucción es igual que simplemente especificar a.

ejemplo final

En un contexto no seguro, los elementos de matriz de matrices unidimensionales se almacenan en orden de índice creciente, empezando por el índice 0 y terminando con el índice Length – 1. En el caso de las matrices multidimensionales, los elementos de matriz se almacenan de forma que los índices de la dimensión situada más a la derecha se incrementan primero, luego la siguiente dimensión izquierda, etc. a la izquierda.

Dentro de una fixed instrucción que obtiene un puntero p a una instancia ade matriz , los valores de puntero que van desde p para p + a.Length - 1 representar direcciones de los elementos de la matriz. Del mismo modo, las variables que van desde p[0] para p[a.Length - 1] representar los elementos de matriz reales. Dada la forma en que se almacenan las matrices, se puede tratar una matriz de cualquier dimensión como si fuera lineal.

Ejemplo:

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($"[{i},{j},{k}] = {a[i,j,k],2} ");
                }
                Console.WriteLine();
            }
        }
    }
}

que genera la salida:

[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

ejemplo final

Ejemplo: en el código siguiente

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);
        }
    }
}

Se usa una fixed instrucción para corregir una matriz para que su dirección se pueda pasar a un método que toma un puntero.

ejemplo final

Un char* valor generado mediante la corrección de una instancia de cadena siempre apunta a una cadena terminada en null. Dentro de una instrucción fija que obtiene un puntero p a una instancia sde cadena , los valores de puntero que van desde p para p + s.Length ‑ 1 representar direcciones de los caracteres de la cadena y el valor p + s.Length de puntero siempre apunta a un carácter NULL (el carácter con el valor '\0').

Ejemplo:

class Test
{
    static string name = "xx";

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

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

ejemplo final

Ejemplo: el código siguiente muestra un fixed_pointer_initializer con una expresión de tipo distinto de array_type o string:

public class C
{
    private int _value;
    public C(int value) => _value = value;
    public ref int GetPinnableReference() => ref _value;
}

public class Test
{
    unsafe private static void Main()
    {
        C c = new C(10);
        fixed (int* p = c)
        {
            // ...
        }
    }
}

El tipo C tiene un método accesible GetPinnableReference con la firma correcta. En la fixed instrucción , el ref int devuelto desde ese método cuando se llama a en c se usa para inicializar el int* puntero p. ejemplo final

La modificación de objetos de tipo administrado a través de punteros fijos puede dar lugar a un comportamiento indefinido.

Nota: Por ejemplo, dado que las cadenas son inmutables, es responsabilidad del programador asegurarse de que los caracteres a los que hace referencia un puntero a una cadena fija no se modifican. nota final

Nota: La terminación automática de valores NULL de las cadenas es especialmente conveniente al llamar a las API externas que esperan cadenas de "estilo C". Tenga en cuenta, sin embargo, que se permite que una instancia de cadena contenga caracteres NULL. Si estos caracteres NULL están presentes, la cadena aparecerá truncada cuando se trate como un objeto terminado char*en null. nota final

24.8 Búferes de tamaño fijo

24.8.1 General

Los búferes de tamaño fijo se usan para declarar matrices en línea de "estilo C" como miembros de estructuras y son principalmente útiles para interactuar con LAS API no administradas.

24.8.2 Declaraciones de búfer de tamaño fijo

Un búfer de tamaño fijo es un miembro que representa el almacenamiento de un búfer de longitud fija de variables de un tipo determinado. Una declaración de búfer de tamaño fijo introduce uno o varios búferes de tamaño fijo de un tipo de elemento determinado.

Nota: Al igual que una matriz, se puede considerar un búfer de tamaño fijo como elementos contenedor. Por lo tanto, el término tipo de elemento tal como se define para una matriz también se usa con un búfer de tamaño fijo. nota final

Los búferes de tamaño fijo solo se permiten en declaraciones de estructura y solo pueden producirse en contextos no seguros (§24.2).

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type
      fixed_size_buffer_declarators ';'
    ;

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

buffer_element_type
    : type
    ;

fixed_size_buffer_declarators
    : fixed_size_buffer_declarator (',' fixed_size_buffer_declarator)*
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

Una declaración de búfer de tamaño fijo puede incluir un conjunto de atributos (§23), un new modificador (§15.3.5), modificadores de accesibilidad correspondientes a cualquiera de las accesibilidades declaradas permitidas para los miembros de estructura (§16.4.3) y un unsafe modificador (§24.2). Los atributos y modificadores se aplican a todos los miembros declarados por la declaración de búfer de tamaño fijo. Es un error para que el mismo modificador aparezca varias veces en una declaración de búfer de tamaño fijo.

No se permite incluir el static modificador una declaración de búfer de tamaño fijo.

El tipo de elemento de búfer de una declaración de búfer de tamaño fijo especifica el tipo de elemento de los búferes introducidos por la declaración. El tipo de elemento de búfer debe ser uno de los tipos predefinidos sbyte, , nintshortushortintulongnuintuintbytecharfloatlong, doubleo .bool

El tipo de elemento de búfer va seguido de una lista de declaradores de búfer de tamaño fijo, cada uno de los cuales presenta un nuevo miembro. Un declarador de búfer de tamaño fijo consta de un identificador que asigna un nombre al miembro, seguido de una expresión constante incluida en [ los tokens y ] . La expresión constante denota el número de elementos del miembro introducido por ese declarador de búfer de tamaño fijo. El tipo de la expresión constante se podrá convertir implícitamente en el tipo inty el valor será un entero positivo distinto de cero.

Los elementos de un búfer de tamaño fijo se colocarán secuencialmente en memoria.

Una declaración de búfer de tamaño fijo que declara varios búferes de tamaño fijo equivale a varias declaraciones de una sola declaración de búfer de tamaño fijo con los mismos atributos y tipos de elementos.

Ejemplo:

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

es equivalente a

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

ejemplo final

24.8.3 Búferes de tamaño fijo en expresiones

La búsqueda de miembros (§12.5) de un miembro de búfer de tamaño fijo continúa exactamente igual que la búsqueda de miembros de un campo.

Se puede hacer referencia a un búfer de tamaño fijo en una expresión mediante un simple_name (§12.8.4), un member_access (§12.8.7) o un element_access (§12.8.12).

Cuando se hace referencia a un miembro de búfer de tamaño fijo como un nombre simple, el efecto es el mismo que un acceso de miembro del formulario this.I, donde I es el miembro de búfer de tamaño fijo.

En un acceso de miembro del formulario E.I donde E. puede ser el implícito this., si E es de un tipo de estructura y una búsqueda de miembro de I en ese tipo de estructura identifica un miembro de tamaño fijo, se E.I evalúa y clasifica de la siguiente manera:

  • Si la expresión E.I no se produce en un contexto no seguro, se produce un error en tiempo de compilación.
  • Si E se clasifica como un valor, se produce un error en tiempo de compilación.
  • De lo contrario, si E es una variable desplazable (§24.4) entonces:
    • Si la expresión E.I es un fixed_pointer_initializer (§24.7), el resultado de la expresión es un puntero al primer elemento del miembro I de búfer de tamaño fijo en E.
    • De lo contrario, si la expresión E.I es un primary_expression (§12.8.12.1) dentro de un element_access (§12.8.12) del formulario E.I[J], el resultado de E.I es un puntero, P, al primer elemento del miembro I de búfer de tamaño fijo en Ey, a continuación, el element_access envolvente se evalúa como el pointer_element_access (§24.6.4). P[J]
    • De lo contrario, se produce un error en tiempo de compilación.
  • De lo contrario, E hace referencia a una variable fija y el resultado de la expresión es un puntero al primer elemento del miembro I de búfer de tamaño fijo en E. El resultado es de tipo S*, donde S es el tipo de elemento de Iy se clasifica como un valor.

Se puede acceder a los elementos posteriores del búfer de tamaño fijo mediante operaciones de puntero desde el primer elemento. A diferencia del acceso a las matrices, el acceso a los elementos de un búfer de tamaño fijo es una operación no segura y no se comprueba el intervalo.

Ejemplo: lo siguiente declara y usa una estructura con un miembro de búfer de tamaño fijo.

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);
    }
}

ejemplo final

24.8.4 Comprobación de la asignación definitiva

Los búferes de tamaño fijo no están sujetos a la comprobación de asignaciones definitiva (§9.4) y los miembros del búfer de tamaño fijo se omiten con fines de comprobación de asignación definitiva de variables de tipo struct.

Cuando la variable de estructura más externa de un miembro de búfer de tamaño fijo es una variable estática, una variable de instancia de una instancia de clase o un elemento de matriz, los elementos del búfer de tamaño fijo se inicializan automáticamente en sus valores predeterminados (§9.3). En todos los demás casos, el contenido inicial de un búfer de tamaño fijo no está definido.

24.9 Asignación de pila

Consulte §12.8.22 para obtener información general sobre el operador stackalloc. Aquí se describe la capacidad de ese operador para dar lugar a un puntero.

Cuando se produce un stackalloc_expression como expresión de inicialización de un local_variable_declaration (§13.6.2), donde el local_variable_type es un tipo de puntero (§24.3) o inferido (var), el resultado del stackalloc_expression es un puntero de tipo T*, donde T es el unmanaged_type del stackalloc_expression. En este caso, el resultado es un puntero al principio del bloque asignado.

En el resto de aspectos, la semántica de local_variable_declarations (§13.6.2) y stackalloc_expressions (§12.8.22) en contextos no seguros siguen las definidas para contextos seguros.

Ejemplo:

unsafe 
{
    // Memory uninitialized
    int* p1 = stackalloc int[3];
    // Memory initialized
    int* p2 = stackalloc int[3] { -10, -15, -30 };
    // Type int is inferred
    int* p3 = stackalloc[] { 11, 12, 13 };
    // Cannot infer context, so pointer result assumed
    var p4 = stackalloc[] { 11, 12, 13 };
    // Error; no conversion exists
    long* p5 = stackalloc[] { 11, 12, 13 };
    // Converts 11 and 13, and returns long*
    long* p6 = stackalloc[] { 11, 12L, 13 };
    // Converts all and returns long*
    long* p7 = stackalloc long[] { 11, 12, 13 };
}

ejemplo final

A diferencia del acceso a matrices o stackallocbloques de tipo "ed Span<T> ", el acceso a los elementos de un stackalloc"bloque ed de tipo de puntero es una operación no segura y no está activada el intervalo.

Ejemplo: en el código siguiente

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        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));
    }
}

Se usa una stackalloc expresión en el IntToString método para asignar un búfer de 16 caracteres en la pila. El búfer se descarta automáticamente cuando el método devuelve.

Tenga en cuenta, sin embargo, que IntToString se puede reescribir en modo seguro; es decir, sin usar punteros, como se indica a continuación:

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        Span<char> buffer = stackalloc char[16];
        int idx = 16;
        do
        {
            buffer[--idx] = (char)(n % 10 + '0');
            n /= 10;
        } while (n != 0);
        if (value < 0)
        {
            buffer[--idx] = '-';
        }
        return buffer.Slice(idx).ToString();
    }
}

ejemplo final

Fin del texto normativo condicionalmente.