jueves, 16 de julio de 2015

Código Inseguro y Punteros en C#

Índice

0. Introducción
1. Fundamentos de Punteros en C#
2. Código Inseguro (Unsafe Code)
3. La Sentencia fixed
4. Operador de Puntero a Miembro: ->
5. Arreglos
5.1 Uso de la palabra reservada stackalloc
5.2 Tamaños fijos de búfer
6. void*
7. Punteros en Código No Administrado
8. Conclusiones
9. Glosario
10. Literatura & Enlaces

0. Introducción

Pasamos a otro de los temas de Avanzandos en C#: en esta oportunidad estudiáremos código inseguro (unsafe code) y apuntadores (pointers).Veremos que a diferencia del código administrado (managed code), el código inseguro requiere de manipulaciones más avanzadas y técnicas (refiriéndonos al tratamiento de direcciones de memoria, liberación de recursos, etc.) y de mayor cuidado por parte del programador. Esta ocasión nos brinda el medio para comprender los fundamentales sobre la manipulación de punteros y sus ventajas a la hora de escribir aplicaciones que requieren inexorablemente un alto desempeño.

1. Fundamentos de Punteros en C#

En C# contamos con las construcciones programáticas conocidas como punteros (o pointers). Este tipo de construcción opera en un contexto inseguro, lo que quiere decir que la gestión automática de memoria de la CLR no está presente y es el programador mismo quien debe encargarse de localizar (allocate) y deslocalizar (deallocate) memoria para las estructuras de datos.

Entre los usos principales de los punteros está la interoperación con APIs diseñadas en el lenguaje de programación C, además [1]:
"..., but may also be used for accesing memory outside the managed heap or for performance-critical hotspots."
Una instancia de un puntero, por otro lado, contiene la dirección de memoria de una variable.
Esquema de un puntero
Figura 1. Esquema de un puntero.
Continuando, los punteros en C# se declaran de la siguiente manera:

type* identificador;
void* identificador; // Permitido pero no recomendado.

Los tipos permitidos para un puntero son los siguientes [2]:
  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, float, double, decimal, o bool.
  • Tipos enum (Enumeraciones en C#).
  • Tipos struct definidos por el usuario (Structs en C#).
Ejemplos de declaraciones válidas de punteros:

int* p; // puntero a un entero
int** p; // puntero a un puntero de un entero
int*[] p; // arreglo unidimensional de punteros de enteros
char* p; // puntero a un caracter
void* p; // puntero a un tipo no definido o desconocido

Entre los operadores principales de punteros tenemos [1]:
Operadores principales de punteros
Figura 2. Operadores principales de punteros [1].
Ejemplo de uso:

Archivo C# BasicosPunteros.cs [Enlace alternativo][Enlace alternativo]:

Sobre la línea 13 declaramos un arreglo de elementos int de longitud 5. El bloque de código que más nos interesa:
  • Línea 17: Marca la región encerrada por unsafe (líneas 17-60) como de tratamiento de código inseguro.
  • Línea 21: Con fixed se marcan las líneas 21-59 no supervisables por el recolector de basura (gargabe collector).
  • Línea 24: Creamos un segundo puntero como copia del puntero puntero1.
  • Línea 27: Mostramos en la salida estándar el valor almacenado por la ubicación de memoria apuntada por puntero2.
  • Línea 30: Pasamos a la región contigua del puntero puntero2 con puntero2 += 1;.
  • Línea 39: Mostramos en la salida estándar el valor almacenado en la ubicación memoria apuntada por puntero2, es decir, el valor del tercer elemento del arreglo arreglo.
  • Línea 44: Muestra en la salida estándar el valor almacenado en la ubicación de memoria apuntada por puntero1; la cual corresponde con el valor del primer elemento del arreglo arreglo.
  • Línea 48: Incrementa en una unidad el valor almacenado en la ubicación de memoria apuntada por puntero1.
El resto de líneas (hasta línea 58) son similares a las operaciones de las líneas 44 y 48. Una vez estemos por fuera de la región de código inseguro mostramos en pantalla el valor actual del primer elemento del arreglo arreglo: 102.

Vale destacar que la sintaxis que hemos usado aquí en C# para para manipular punteros es similar a la de C++ [6].

Compilación:

  1. csc /target:exe /unsafe BasicosPunteros.cs

Nota: No olvidar especificar el argumento /unsafe al compilar, de lo contrario el compilador generará el error CS0227 [7].

Ejecución assembly:

  1. .\UnicaInstanciaAplicacion.exe

> Prueba de ejecución:
Ejecución assembly BasicosPunteros.exe
Figura 2. Ejecución assembly BasicosPunteros.exe.

2. Código Inseguro (Unsafe Code)

Con Unsafe code o código inseguro nos referimos a una región de código en la que está permitada la declaración y operaciones con punteros (como ya lo vimos en el ejemplo de la sección anterior). La palabra clave para marcar un método, miembro, o bloque es unsafe. A continuación un método de ejemplo que es marcado con unsafe:

unsafe void AplicarFiltroAzul([,] bitmap)
{
int tamanio = bitmap.Length;

fixed(int* b = bitmap)
{
int* p = b;

for (int i = 0; i < tamanio; ++i)
{
*p++ &= 0xFF;
}
}
}

Notemos que el cuerpo del método AplicarFiltroAzul hace uso de operadores de punteros para modificar regiones de memoria del objeto bitmap.


Respecto al contraste con una implementación segura [1]:
"Unsafe code can run faster than a corresponding safe implementation. In this case, the code would have required a nested loop with array indexing and bounds checking."

3. La sentencia fixed

La sentencia fixed [6] marca un objeto como inamovible en una región de memoria; esto con el propósito de evitar el desgaste o fragmentación de memoria producido por el colector de basura [1].

Respecto al uso excesivo de fixed:
"...this may have an impact on the efficiency of the runtime, so fixed blocks should be used only briefly, and heap allocation should be avoided within the fixed block."
Ejemplo de uso:

Archivo C# Usofixed.cs [Enlace alternativo][Enlace alternativo]:
Con unsafe (línea 16) marcamos la región encerrada por { y } como código inseguro. Con fixed (línea 20) indicamos a la CLR que la región de código para el miembro campo deberá permanecer fija.

Compilación:


  1. csc /target:exe /unsafe Usofixed.cs

Ejecución assembly:


  1. .\Usofixed.exe

4. Operador de Puntero a Miembro: ->

-> es otro de los operador disponibles para el trabajo con punteros, además de los operadores & y *. Con este operador podemos acceder a un miembro de un tipo y realizar operaciones con la sintaxis de punteros.

Ejemplo de uso:

Archivo C# UsoOperadorMiembro.cs [Enlace alternativo][Enlace alternativo]:

Compilación:


  1. csc /target:exe /unsafe UsoOperadorMiembro.cs

Ejecución assembly:


  1. .\UsoOperadorMiembro.exe

5. Arreglos

5.1 Uso de la palabra reservada stackalloc

La palabra reservada stackalloc [9] en el contexto de código inseguro (unsafe) sirve para asignar un bloque de memoria sobre el stack (Boxing y Unboxing en C#):

int* block = stackalloc int[100];

Debemos considerar que el hecho de que la localización se efectúe sobre el stack, luego su ciclo de vida o alcance estará disponible mientras que el método se ejecuta (como sucede con las variables locales).

Otro ejemplo de uso básico:

int* bloque = stackalloc int [10];
for (int i = 0; i < 10; ++i)
{
Console.WriteLine (bloque[i]); // Imprime datos de memoria en bruto
}

5.2 Tamaños fijos de búfer

La palabra fixed es contextual, y para este caso permite la creación búfers de tamaño fijo en la declaración de un struct:

En este código de ejemplo vale destacar sobretodo la definición del struct en las líneas 5-9:
  • Línea 8: crea un búfer de capacidad fija igual a 30.
  • Líneas 15-26: definición del constructor Entidad en el que se inicializa cada espacio del bloque de memoria creado en la línea 8.

6. void*

Con void* obviamos el tipo de datos que manipulará el puntero, y como se manifiesta en [1]:
"...and it is useful for functions that deal with raw memory."
; un puntero void es útil para trabajar con memoria en crudo (raw).

7. Punteros en Código No Administrado

Otras de las utilidades de los punteros [1]:
  • Acceso a datos que no está al alcance de la head administrada: librerías de vínculo dinámico (DLL [11]) o COM [12].
  • Manipulación de datos que no residen en la memoria de trabajo: memoria de gráficos o medio de almacenamiento de un dispositivo embebido.

8. Conclusiones

El trabajo con punteros como hemos comprobado requiere de un enfoque diferente en lo que se refiere a la manipulación de datos directamente sobre la memoria, asunto distinto del código administrado, ya que éste se encarga de las operaciones detrás de cámara pero con un coste en el rendimiento. A medida que vayamos más a fondo en C# estudiáremos con más detalle el uso de punteros para la creación de programas de más alto rendimiento o la elaboración de soluciones que sin el uso de punteros serían imposibles de construir.


El próximo artículo C# lo dedicaremos a estudiar las directivas de pre-procesador.

9. Glosario

  • API
  • Arreglo
  • Bloque
  • Búfer
  • CLR
  • Código inseguro
  • Dirección
  • Dispositivo embebido
  • Memoria
  • Puntero

10. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Pointer types (C# Programming Guide) - https://msdn.microsoft.com/en-us/library/y31yhkeb.aspx
[3]: Enumeraciones en C# - http://ortizol.blogspot.com/2014/05/enumeraciones-en-c-parte-1.html
[4]: Structs en C# - http://ortizol.blogspot.com/2014/04/structs-en-c.html
[5]: unsafe (C# Reference) - https://msdn.microsoft.com/en-us/library/chfa2zb8.aspx
[6]: fixed Statement (C# Reference) - https://msdn.microsoft.com/en-us/library/vstudio/f58wzh21(v=vs.100).aspx
[7]: Compiler Error CS0227 - https://msdn.microsoft.com/en-us/library/ezb5hwx9(v=vs.90).aspx
[8]: C++ - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/C%2B%2B
[9]: stackalloc (C# Reference) - https://msdn.microsoft.com/en-us/library/cx9s2sy4.aspx
[10]: Boxing y Unboxing en C# - http://ortizol.blogspot.com/2014/03/boxing-y-unboxing-en-c.html
[11]: Dynamic-link library - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/Dynamic-link_library
[12]: Component Object Model - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/Component_Object_Model


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.