Operadores relacionados con código inseguro

Operador sizeof. Obtención de tamaño de tipo

    El operador unario y prefijo sizeof devuelve un objeto int con el tamaño en bytes del tipo de dato sobre el que se aplica. Sólo puede aplicarse en contextos inseguros y sólo a tipos de datos para los que sea posible definir punteros, siendo su sintaxis de uso:


sizeof(
<tipo>)

    Cuando se aplica a tipos de datos básicos su resultado es siempre constante. Por ello, el compilador optimiza dichos usos de sizeof sustituyéndolos internamente por su valor (inlining) y considerando que el uso del operador es una expresión constante. Estas constantes correspondientes a los tipos básicos son las indicadas en la Tabla 10 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F00520065006600340038003600360039003600380037003800000000 :

Tipos

Resultado

sbyte, byte, bool

1

short, ushort, char

2

int, uint, float

4

long, ulong, double

8

Tabla 10: Resultados de sizeof para tipos básicos

    Para el resto de tipos a los que se les puede aplicar, sizeof no tiene porqué devolver un resultado constante sino que los compiladores pueden alinear en memoria las estructuras incluyendo bits de relleno cuyo número y valores sean en principio indeterminado. Sin embargo, el valor devuelto por sizeof siempre devolverá el tamaño en memoria exacto del tipo de dato sobre el que se aplique, incluyendo bits de relleno si los tuviese.

    Nótese que es fácil implementar los operadores de aritmética de punteros usando sizeof. Para ello, ++ se definiría como añadir a la dirección almacenada en el puntero el resultado de aplicar sizeof a su tipo de dato, y -- consistiría en restarle dicho valor. Por su parte, el operador + usado de la forma P + N (P es un puntero de tipo T y N un entero) lo que devuelve es el resultado de añadir al puntero sizeof(T)*N, y P – N devuelve el resultado de restarle sizeof(T)*N. Por último, si se usa - para restar dos punteros P1 y P2 de tipo T, ello es equivalente a calcular (((long)P1) - ((long)P2)))/sizeof(T)

Operador stackalloc. Creación de tablas en pila.

    Cuando se trabaja con punteros puede resultar interesante reservar una zona de memoria en la pila donde posteriormente se puedan ir almacenando objetos. Precisamente para eso está el operador stackalloc, que se usa siguiéndose la siguiente sintaxis:


stackalloc
<tipo>[<número>]

    stackalloc reserva en pila el espacio necesario para almacenar contiguamente el número de objetos de tipo <tipo> indicado en <número> (reserva sizeof(<tipo>)*<número> bytes) y devuelve un puntero a la dirección de inicio de ese espacio. Si no quedase memoria libre suficiente para reservarlo se produciría una excepción System.StackOverflowException.

    stackalloc sólo puede usarse para inicializar punteros declarados como variables locales y sólo en el momento de su declaración.. Por ejemplo, un puntero pt que apuntase al principio de una región con capacidad para 100 objetos de tipo int se declararía con:


int * pt = stackalloc int[100];

Sin embargo, no sería válido hacer:


int * pt;
pt = stackalloc int[100];//ERROR:Sólo puede usarse stackalloc
//en declaraciones

    Aunque pueda parecer que stackalloc se usa como sustituto de new para crear tablas en pila en lugar de en memoria dinámica, no hay que confundirse: stackalloc sólo reserva un espacio contiguo en pila para objetos de un cierto tipo, pero ello no significa que se cree una tabla en pila. Las tablas son objetos que heredan de System.Array y cuentan con los miembros heredados de esta clase y de object, pero regiones de memoria en pila reservadas por stackalloc no. Por ejemplo, el siguiente código es inválido.

 
 int[] tabla;
 int * pt = stackalloc int[100];
 tabla = *pt;  //ERROR: El contenido de pt es un int, no una tabla (int[])
 Console.WriteLine(pt->Length); // ERROR: pt no apunta a una tabla

    Sin embargo, gracias a que como ya se ha comentado en este tema el operador [] está redefinido para trabajar con punteros, podemos usarlo para acceder a los diferentes objetos almacenados en las regiones reservadas con stackalloc como si fuesen tablas. Por ejemplo, este código guarda en pila los 100 primeros enteros y luego los imprime:

 
 class Stackalloc
 {
  public unsafe static void Main()
  {
   int * pt = stackalloc int[100];
   for (int i=0; i<100; i++)
    pt[i] = i;
   for(int i=0; i<100; i++)
    System.Console.WriteLine(pt[i]);
  }
 }

    Nótese que, a diferencia de lo que ocurriría si pt fuese una tabla, en los accesos con pt[i] no se comprueba que i no supere el número de objetos para los que se ha reservado memoria. Como contrapartida, se tiene el inconveniente de que al no ser pt una tabla no cuenta con los métodos típicos de éstas y no puede usarse foreach para recorrerla.

    Otra ventaja de la simulación de tablas con stackalloc es que se reserva la memoria mucho más rápido que el tiempo que se tardaría en crear una tabla. Esto se debe a que reservar la memoria necesaria en pila es tan sencillo como incrementar el puntero de pila en la cantidad correspondiente al tamaño a reservar, y no hay que perder tiempo en solicitar memoria dinámica. Además, stackalloc no pierde tiempo en inicializar con algún valor el contenido de la memoria, por lo que la "tabla" se crea antes pero a costa de que luego sea más inseguro usarla ya que hay que tener cuidado con no leer trozos de ella antes de asignarles valores válidos.

Operadores relacionados con código inseguro
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:13145
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com