Fijación de variables apuntadas

    Aunque un puntero sólo puede apuntar a datos de tipos que puedan almacenarse completamente en pila (o sea, que no sean ni objetos de tipos referencia ni estructuras con miembros de tipos referencia), nada garantiza que los objetos apuntados en cada momento estén almacenados en pila. Por ejemplo, las variables estáticas de tipo int o los elementos de una tabla de tipo int se almacenan en memoria dinámica aún cuando son objetos a los que se les puede apuntar con punteros.

    Si un puntero almacena la dirección de un objeto almacenado en memoria dinámica y el recolector de basura cambia al objeto de posición tras una compactación de memoria resultante de una recolección, el valor almacenado en el puntero dejará de ser válido. Para evitar que esto ocurra se puede usar la instrucción fixed, cuya sintaxis de uso es:


fixed(
<tipo> <declaraciones>)
      <instrucciones>

    El significado de esta instrucción es el siguiente: se asegura que durante la ejecución del bloque de <instrucciones> indicado el recolector de basura nunca cambie la dirección de ninguno de los objetos apuntados por los punteros de tipo <tipo> declarados. Estas <declaraciones> siempre han de incluir una especificación de valor inicial para cada puntero declarado, y si se declaran varios se han de separar con comas.

    Los punteros declarados en <declaraciones> sólo existirán dentro de <instrucciones>, y al salir de dicho bloque se destruirán. Además, si se les indica como valor inicial una tabla o cadena que valga null saltará una NullReferenceException. También hay que señalar que aunque sólo pueden declarase punteros de un mismo tipo en cada fixed, se puede simular fácilmente la declaración de punteros de distintos tipos anidando varios fixed.

    Por otro lado, los punteros declarados en <declaraciones> son de sólo lectura, ya que si no podría cambiárseles su valor por el de una dirección de memoria no fijada y conducir ello a errores difíciles de detectar.

    Un uso frecuente de fixed consiste en apuntar a objetos de tipos para los que se puedan declarar punteros pero que estén almacenados en tablas, ya que ello no se puede hacer directamente debido a que las tablas se almacenan en memoria dinámica. Por ejemplo, copiar usando punteros una tabla de 100 elementos de tipo int en otra se haría así:


 class CopiaInsegura
 {
  public unsafe static void Main()
  {
   int[] tOrigen = new int[100];
   int[] tDestino = new int[100];
   fixed (int * pOrigen=tOrigen, pDestino=tDestino)
   {
    for (int i=0; i<100; i++)
     pOrigen[i] = pDestino[i];
   }
  }
 }

    Como puede deducirse del ejemplo, cuando se inicializa un puntero con una tabla, la dirección almacenada en el puntero en la zona <declaraciones> del fixed es la del primer elemento de la tabla (también podría haberse hecho pOrigen = &tOrigen[0]), y luego es posible usar la aritmética de punteros para acceder al resto de elementos a partir de la dirección del primero ya que éstos se almacenan consecutivamente.

    Al igual que tablas, también puede usarse fixed para recorrer cadenas. En este caso lo que hay que hacer es inicializar un puntero de tipo char * con la dirección del primer carácter de la cadena a la que se desee que apunte tal y como muestra este ejemplo en el que se cambia el contenido de una cadena "Hola" por "XXXX":


 class CadenaInsegura
 {
  public unsafe static void Main()
  {
   string s="Hola";
   Console.WriteLine("Cadena inicial: {0}", s);
   fixed (char * ps=s)
   {
    for (int i=0;i<s.Length;i++)
     ps[i] = 'A';
   }
   Console.WriteLine("Cadena final: {0}", s);
  }
 }

    La salida por pantalla de este último programa es:

      Hola

      AAAA

    La ventaja de modificar la cadena mediante punteros es que sin ellos no sería posible hacerlo ya que el indizador definido para los objetos string es de sólo lectura.

    Cuando se modifiquen cadenas mediante punteros hay que tener en cuenta que, aunque para facilitar la comunicación con código no gestionado escrito en C o C++ las cadenas en C# también acaban en el carácter ‘\0’, no se recomienda confiar en ello al recorrerlas con punteros porque ‘\0’ también puede usarse como carácter de la cadena. Por ello, es mejor hacer como en el ejemplo y detectar su final a través de su propiedad Length.

    Hay que señalar que como fixed provoca que no pueda cambiarse de dirección a ciertos objetos almacenados en memoria dinámica, ello puede producir la generación de huecos en memoria dinámica, lo que tiene dos efectos muy negativos:

  • El recolector de basura está optimizado para trabajar con memoria compactada, pues si todos los objetos se almacenan consecutivamente en memoria dinámica crear uno nuevo es tan sencillo como añadirlo tras el último. Sin embargo, fixed rompe esta consecutividad y la creación de objetos en memoria dinámica dentro de este tipo de instrucciones es más lenta porque hay que buscar huecos libres.
  • Por defecto, al eliminarse objetos de memoria durante una recolección de basura se compacta la memoria que queda ocupada para que todos los objetos se almacenen en memoria dinámica. Hacer esto dentro de sentencias fixed es más lento porque hay que tener en cuenta si cada objeto se puede o no mover.

    Por estas razones es conveniente que el contenido del bloque de instrucciones de una sentencia fixed sea el mínimo posible, para que así el fixed se ejecute lo antes posible.

Fijación de variables apuntadas
José Antonio González Seco

José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.
Fecha de alta:07/12/2006
Última actualizacion:07/12/2006
Visitas totales:10396
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com