Anda di halaman 1dari 18

Nombres y Apellidos: Bryan de Jesús Soto Linarte

Año, Carrera y Grupo: 3 año, IME, Grupo #1

Curso: Lenguaje de Programación II

Docente: Ing. Skarleth Fletes

Tema: Código inseguro: Punteros en C#.

Fecha de Entrega: 22/02/2019


Introducción

Dentro de los Lenguajes de Programación podremos encontrar muchas variantes


para poder crear soluciones a los problema que se nos presenten, computacional
e informáticamente hablando. Muchas son las partes y conceptos que debemos
conocer y manejar de los lenguajes de programación, entre estos tenemos el
siguiente:

Código inseguro es todo aquél fragmento de código en C# dentro del cual es


posible hacer uso de punteros. Un puntero en C# es una variable que es capaz de
almacenar direcciones de memoria.

Generalmente suele usarse para almacenar direcciones que almacenen objetos,


por lo que en esos casos su significado es similar al de variables normales de
tipos referencia. Sin embargo, los punteros no cuentan con muchas de las
restricciones de éstas a la hora de acceder al objeto.

Aparte de su mayor eficiencia, también hay ciertos casos en que es necesario


disponer del código inseguro, como cuando se desea hacer llamadas a funciones
escritas en lenguajes no gestionados cuyos parámetros tienen que ser punteros.
Objetivos:

General:
● Definir qué son los punteros y describir su rol en la
resolución de problemas.

Específicos:
● Presentar la Definición de punteros.
● Mostrar el cómo se da la Manipulación de punteros.
● Explicar el Aritmética de punteros.
● Enseñar los Operadores relacionados con un código
inseguro.
● Proponer un ejemplo sobre el uso de punteros en C#.
Desarrollo:

C# (pronunciado si sharp en inglés) es un lenguaje de programación orientado a


objetos desarrollado y estandarizado por Microsoft como parte de su plataforma
.NET.. C# es uno de los lenguajes de programación diseñados para la
infraestructura de lenguaje común. Su sintaxis básica deriva de C/C++ y utiliza
el modelo de objetos de la plataforma .NET, similar al de Java, aunque incluye
mejoras derivadas de otros lenguajes, entre esas mejoras podremos encontrar la
siguiente:

Los Punteros en C#, Un “puntero” es una dirección de memoria. Normalmente


un puntero tendrá un tipo de datos asociado: por ejemplo, un “puntero a entero”
será una dirección de memoria en la que habrá almacenado (o podremos
almacenar) un número entero.

El hecho de poder acceder directamente al contenido de ciertas posiciones de


memoria da una mayor versatilidad a un programa, porque permite hacer casi
cualquier cosa, pero a cambio de un riesgo de errores mucho mayor.

En lenguajes como C, es imprescindible utilizar punteros para poder crear


estructuras dinámicas, pero en C# podemos “esquivarlos”, dado que tenemos
varias estructuras dinámicas ya creadas como parte de las bibliotecas auxiliares
que acompañan al lenguaje básico.

Pero aun con esta mejora se nos presenta la siguiente situación:


Como mencionamos antes un Código Inseguro es todo aquél fragmento de
código en C# dentro del cual es posible hacer uso de punteros y un Puntero en
C# es una variable que es capaz de almacenar direcciones de memoria.
Generalmente suele usarse para almacenar direcciones que almacenen objetos,
por lo que en esos casos su significado es similar al de variables normales de
tipos referencia.

Pero el uso de punteros hace el código más proclive a fallos en tanto que se salta
muchas de las medidas incluidas en el acceso normal a objetos, por lo que es
necesario incluir ciertas medidas de seguridad que eviten la introducción
accidental de esta inseguridad, como los punteros son “peligrosos” (es frecuente
que den lugar a errores muy difíciles de encontrar), el compilador nos obliga a
que le digamos que sabemos que esa zona de programa no es segura, usando la
palabra “unsafe”:
Manipulación de los Punteros:

1. Obtención de dirección de memoria. Operador &

Para almacenar una referencia a un objeto en un puntero se puede aplicar al


objeto el operador prefijo &, que lo que hace es devolver la dirección que en
memoria ocupa el objeto sobre el que se aplica. Este operador no es aplicable a
expresiones constantes, pues éstas no se almacenan en ninguna dirección de
memoria específica sino que se incrustan en las instrucciones.

Tampoco es válido aplicar & a campos readonly, pues si estos pudiesen ser
apuntados por punteros se correría el riesgo de poderlos modificar ya que a
través de un puntero se accede a memoria directamente, sin tenerse en cuenta si
en la posición accedida hay algún objeto, por lo que mucho menos se
considerará si éste es de sólo lectura. Lo que es sí válido es almacenar en un
puntero es la dirección de memoria apuntada por otro puntero. En ese caso
ambos punteros apuntarán al mismo objeto y las modificaciones a éste
realizadas a través de un puntero también afectarían al objeto visto por el otro,
de forma similar a como ocurre con las variables normales de tipos referencia.
Es más, los operadores relacionales típicos (==, !=, <, >, <= y >=) se han
redefinido para que cuando se apliquen entre dos punteros de cualesquiera dos
tipos lo que se compare sean las direcciones de memoria que estos almacenan.
2. Acceso a contenido de puntero. Operador *

Un puntero no almacena directamente un objeto sino que suele almacenar la


dirección de memoria de un objeto (o sea, apunta a un objeto) Para obtener a
partir de un puntero el objeto al que apunta hay que aplicarle al mismo el
operador prefijo *, que devuelve el objeto apuntado.

Es posible en un puntero almacenar null para indicar que no apunta a ninguna


dirección válida. Sin embargo, si luego se intenta acceder al contenido del
mismo a través del operador * se producirá generalmente una excepción de tipo
NullReferenceException (aunque realmente esto depende de la
implementación del lenguaje). No tiene sentido aplicar * a un puntero de tipo
void * ya que estos punteros no almacenan información sobre el tipo de objetos
a los que apuntan y por tanto no es posible recuperarlos a través de los mismos
ya que no se sabe cuánto espacio en memoria a partir de la dirección
almacenada en el puntero ocupa el objeto apuntado y, por tanto, no se sabe
cuánta memoria hay que leer para obtenerlo.

3. Acceso a miembro de contenido de puntero. Operador ->

Si un puntero apunta a un objeto estructura que tiene un método F() sería


posible llamarlo a través del puntero con:

(*objeto).F();

Sin embargo, como llamar a objetos apuntados por punteros es algo bastante
habitual, para facilitar la sintaxis con la que hacer esto se ha incluido en C# el
operador ->, con el que la instrucción anterior se escribiría así:
objeto->f();

Es decir, del mismo modo que el operador . permite acceder a los miembros de
un objeto referenciado por una variable normal, -> permite acceder a los
miembros de un objeto referenciado por un puntero. En general, un acceso de la
forma O -> M es equivalente a hacer (*O).M. Por tanto, al igual que es
incorrecto aplicar * sobre punteros de tipo void *, también lo es aplicar ->

4. Conversiones de punteros

De todo lo visto hasta ahora parece que no tiene mucho sentido el uso de
punteros de tipo void * Pues bien, una utilidad de este tipo de punteros es que
pueden usarse como almacén de punteros de cualquier otro tipo que luego
podrán ser recuperados a su tipo original usando el operador de conversión
explícita. Es decir, igual que los objetos de tipo object pueden almacenar
implícitamente objetos de cualquier tipo, los punteros void * pueden almacenar
punteros de cualquier tipo y son útiles para la escritura de métodos que puedan
aceptar parámetros de cualquier tipo de puntero.

A diferencia de lo que ocurre entre variables normales, las conversiones entre


punteros siempre se permiten, al realizarlas nunca se comprueba si son válidas.

Del mismo modo, conversiones entre punteros pueden terminar produciendo


que un puntero apunte a un objeto de mayor tamaño que los objetos del tipo del
puntero. En estos casos, el puntero apuntaría a los bits menos significativos del
objeto apuntado. También es posible realizar conversiones entre punteros y
tipos básicos enteros. La conversión de un puntero en un tipo entero devuelve la
dirección de memoria apuntada por el mismo.
Aritmética de Punteros:

Los punteros se suelen usar para recorrer tablas de elementos sin necesidad de
tener que comprobarse que el índice al que se accede en cada momento se
encuentra dentro de los límites de la tabla. Por ello, los operadores aritméticos
definidos para los punteros están orientados a facilitar este tipo de recorridos.

Hay que tener en cuenta que todos los operadores aritméticos aplicables a
punteros dependen del tamaño del tipo de dato apuntado, por lo que no son
aplicables a punteros void * ya que estos no almacenan información sobre dicho
tipo. Esos operadores son:

❖ ++ y --: El operador ++ no suma uno a la dirección almacenada en un


puntero, sino que le suma el tamaño del tipo de dato al que apunta. Así, si
el puntero apuntaba a un elemento de una tabla pasará a apuntar al
siguiente (los elementos de las tablas se almacenan en memoria
consecutivamente) Del mismo modo, -- resta a la dirección almacenada
en el puntero el tamaño de su tipo de dato. El problema que puede
plantear en ciertos casos el uso de ++ y -- es que hacen que al final del
recorrido el puntero deje de apuntar al primer elemento de la tabla. Ello
podría solucionarse almacenando su dirección en otro puntero antes de
iniciar el recorrido y restaurandola a partir de él tras finalizarlo.

❖ + y -: Permiten solucionar el problema de ++ y -- antes comentado de una


forma más cómoda basada en sumar o restar un cierto entero a los
punteros. + devuelve la dirección resultante de sumar a la dirección
almacenada en el puntero sobre el que se aplica el tamaño del tipo de
dicho puntero tantas veces como indique el entero sumado. - tiene el
mismo significado pero r estando dicha cantidad en vez de sumarla. El
operador - también puede aplicarse entre dos punteros de un mismo tipo,
caso en que devuelve un long que indica cuántos elementos del tipo del
puntero pueden almacenarse entre las direcciones de los punteros
indicados.

❖ []: Dado que es frecuente usar + para acceder a elementos de tablas,


también se ha redefinido el operador [] para que cuando se aplique a una
tabla haga lo mismo y devuelva el objeto contenido en la dirección
resultante. No hay que confundir el acceso a los elementos de una tabla
aplicando [] sobre una variable de tipo tabla normal con el acceso a través
de un puntero que apunte a su primer elemento. En el segundo caso no se
comprueba si el índice indicado se encuentra dentro del rango de la tabla,
con lo que el acceso es más rápido pero también más proclive a errores
difíciles de detectar.

Finalmente, respecto a la aritmética de punteros, hay que tener en cuenta que


por eficiencia, en las operaciones con punteros nunca se comprueba si se
producen desbordamientos, y en caso de producirse se truncan los resultados sin
avisar de ello mediante excepciones. Por eso hay que tener especial cuidado al
operar con punteros no sea que un desbordamiento no detectado cause errores
de causas difíciles de encontrar.

Operadores Relacionados Con Un 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. Cuando se aplica a tipos de datos básicos su resultado es siempre
constante. Por ello, el compilador optimiza dichos usos de sizeof
sustituyendolos 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 siguiente tabla:

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

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. 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, 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.
Ejemplo de Punteros:

using System;
public class BasicosPunteros
{
public static void Main()
{
Console.WriteLine(Environment.NewLine);
// Declaración de un arreglo de enteros como objetos
// (managed code):
int[] arreglo = new int[5] {100, 200, 300, 400, 500};
// El uso de punteros requiere marcar la siguiente región con la palabra clave
unsafe:
{
// Evita al colector de basura relocalizar una variable desplazable:
fixed(int* puntero1 = &arreglo[0]
{
// Creación de un nuevo puntero para incrementar la ubicación de memoria:
int* puntero2 = puntero1;

// Muestra el valor de la memoria apuntada por `puntero2`:


Console.WriteLine(*puntero2);

// Incrementa en 1 la región de memoria apuntada por `puntero2`:


puntero2 += 1;

// Se muestra el valor del segundo elemento de `arreglo`:


Console.WriteLine(*puntero2);
// Incrementa en 1 la región de memoria apuntada por `puntero2`:
puntero2 += 1;

// Se muestra el valor del tercer elemento de `arreglo`:


Console.WriteLine(*puntero2);

Console.WriteLine("-----------");

// Se muestra el valor actual apuntado por `puntero1`:


Console.WriteLine(*puntero1);

// Incrementa en una unidad el valor de la región de memoria apuntada por


`puntero1`:
*puntero1 += 1;

// Muestra el valor del primer elemento de `arreglo`:


Console.WriteLine(*puntero1);

// Incrementa en una unidad el valor de la región de memoria apuntada por


`puntero1`:
*puntero1 += 1;

// Muestra el valor del primer elemento de `arreglo`:


Console.WriteLine(*puntero1);
}
}
Console.WriteLine(Environment.NewLine);
Console.WriteLine("-----------");
// Por fuera de la región de código inseguro:
Console.WriteLine(arreglo[0]);

Console.WriteLine(Environment.NewLine);
}
}
Conclusión:

Los punteros son una de las más potentes características de C, pero a la vez uno
de sus mayores peligros. Los punteros nos permiten acceder directamente a
cualquier parte de la memoria. Esto da a los programas C una gran potencia. Sin
embargo son una fuente ilimitada de errores. Un error usando un puntero puede
bloquear el sistema, y a veces puede ser difícil detectarlo. Otros lenguajes no
nos dejan usar punteros para evitar estos problemas, pero a la vez nos quitan
parte del control que tenemos en C.

Los punteros son uno de los tipos de datos más temidos de C. Sin embargo,
también son los más versátiles y los que mayor potencia proporcionan. Su uso
permite el paso de argumentos por referencia, la construcción de estructuras
dinámicas de datos y la mejora en la velocidad de ejecución de algunos
algoritmos. Sin embargo, mal usados pueden producir errores graves, difíciles
de detectar y, en ocasiones, los peores errores posibles: los no reproducibles.

Es por eso que conocer su estructura, formas de manejo y ejecución así como su
declaración, es de vital importancia para la implementación de un programa y
así poder desarrollar una solución eficaz para un problema determinado que se
nos presente y necesita una respuesta por medio de este concepto de Punteros
que traerá consigo los Códigos Inseguros.
Bibliografía:

[1]J. González Seco, "Código inseguro", Devjoker.com, 2006. [Online].


Available: http://www.devjoker.com/contenidos/catss/178/Codigo-
inseguro.aspx. [Accessed: 22- Feb- 2019].

[2]J. González Seco, "Manipulación de punteros", Devjoker.com, 2006.


[Online]. Available:
http://www.devjoker.com/contenidos/catss/180/Manipulacion-de-punteros.aspx.
[Accessed: 22- Feb- 2019].

[4]J. Ortíz Ordonez, "Uso de punteros en C#. Ejemplo básico.", Gist, 2015.
[Online]. Available: https://gist.github.com/Fhernd/1fbe10aae1efe4fc0923.
[Accessed: 22- Feb- 2019].

[5]V. Gonzalez, "Punteros", Platea.pntic.mec.es, 2009. [Online]. Available:


http://platea.pntic.mec.es/vgonzale/cyr_0204/cyr_01/control/lengua_C/punteros.
htm. [Accessed: 22- Feb- 2019].

[6]Uco.es, 2002. [Online]. Available:


http://www.uco.es/grupos/eatco/informatica/metodologia/mtptema13.pdf.
[Accessed: 22- Feb- 2019].

Anda mungkin juga menyukai