C# Language
Punteros
Buscar..
Observaciones
Punteros e unsafe
Debido a su naturaleza, los punteros producen código no verificable. Por lo tanto, el uso de cualquier tipo de puntero requiere un contexto unsafe
.
El tipo System.IntPtr
es una envoltura segura alrededor de un void*
. Está pensado como una alternativa más conveniente para void*
cuando no se requiere un contexto inseguro para realizar la tarea en cuestión.
Comportamiento indefinido
Al igual que en C y C ++, el uso incorrecto de los punteros puede invocar un comportamiento indefinido, con posibles efectos secundarios como corrupción de la memoria y ejecución de código no deseado. Debido a la naturaleza no verificable de la mayoría de las operaciones de puntero, el uso correcto de los punteros es responsabilidad exclusiva del programador.
Tipos que soportan punteros
A diferencia de C y C ++, no todos los tipos de C # tienen los tipos de punteros correspondientes. Un tipo T
puede tener un tipo de puntero correspondiente si se aplican los dos criterios siguientes:
-
T
es un tipo de estructura o un tipo de puntero. -
T
contiene solo miembros que satisfacen estos dos criterios recursivamente.
Punteros para acceso a la matriz
Este ejemplo demuestra cómo se pueden usar los punteros para el acceso tipo C a las matrices C #.
unsafe
{
var buffer = new int[1024];
fixed (int* p = &buffer[0])
{
for (var i = 0; i < buffer.Length; i++)
{
*(p + i) = i;
}
}
}
La palabra clave unsafe
es necesaria porque el acceso del puntero no emitirá ninguna verificación de límites que normalmente se emite al acceder a las matrices de C # de forma regular.
La palabra clave fixed
le dice al compilador de C # que emita instrucciones para fijar el objeto de una manera segura y excepcional. La fijación es necesaria para garantizar que el recolector de basura no moverá la matriz en la memoria, ya que esto invalidaría cualquier puntero que apunte dentro de la matriz.
Aritmética de punteros
La suma y la resta en los punteros funciona de manera diferente a los enteros. Cuando un puntero aumenta o disminuye, la dirección a la que apunta aumenta o disminuye según el tamaño del tipo de referencia.
Por ejemplo, el tipo int
(alias para System.Int32
) tiene un tamaño de 4. Si se puede almacenar un int
en la dirección 0, el int
posterior se puede almacenar en la dirección 4, y así sucesivamente. En codigo:
var ptr = (int*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 4
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8
De manera similar, el tipo long
(alias para System.Int64
) tiene un tamaño de 8. Si se puede almacenar un long
en la dirección 0, el long
posterior puede almacenarse en la dirección 8, y así sucesivamente. En codigo:
var ptr = (long*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 16
El tipo void
es especial y los punteros void
también son especiales y se utilizan como punteros de captura cuando el tipo no se conoce o no importa. Debido a su naturaleza de tamaño agnóstico, los punteros de void
no se pueden incrementar o disminuir:
var ptr = (void*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));
El asterisco es parte del tipo.
En C y C ++, el asterisco en la declaración de una variable de puntero es parte de la expresión que se declara. En C #, el asterisco en la declaración es parte del tipo .
En C, C ++ y C #, el siguiente fragmento de código declara un puntero int
:
int* a;
En C y C ++, el siguiente fragmento de código declara un puntero int
y una variable int
. En C #, declara dos punteros int
:
int* a, b;
En C y C ++, el siguiente fragmento de código declara dos punteros int
. En C #, no es válido:
int *a, *b;
vacío*
C # hereda de C y C ++ el uso de void*
como un puntero de tipo agnóstico y de tamaño.
void* ptr;
Cualquier tipo de puntero se puede asignar a void*
mediante una conversión implícita:
int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;
Lo contrario requiere una conversión explícita:
int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;
int* p2 = (int*)ptr;
Acceso de miembros usando ->
C # hereda de C y C ++ el uso del símbolo ->
como un medio para acceder a los miembros de una instancia a través de un puntero escrito.
Considere la siguiente estructura:
struct Vector2
{
public int X;
public int Y;
}
Este es un ejemplo del uso de ->
para acceder a sus miembros:
Vector2 v;
v.X = 5;
v.Y = 10;
Vector2* ptr = &v;
int x = ptr->X;
int y = ptr->Y;
string s = ptr->ToString();
Console.WriteLine(x); // prints 5
Console.WriteLine(y); // prints 10
Console.WriteLine(s); // prints Vector2
Punteros genéricos
Los criterios que debe cumplir un tipo para admitir punteros (ver Comentarios ) no pueden expresarse en términos de restricciones genéricas. Por lo tanto, cualquier intento de declarar un puntero a un tipo proporcionado a través de un parámetro de tipo genérico fallará.
void P<T>(T obj)
where T : struct
{
T* ptr = &obj; // compile-time error
}