visual videos usar punteros estructuras como ats apuntadores c++ c pointers x86 x86-16

videos - punteros en visual c++



¿Qué son punteros cercanos, lejanos y enormes? (6)

¿Alguien puede explicarme estos indicadores con un ejemplo adecuado ... y cuándo se usan estos indicadores?


El ejemplo principal es la arquitectura Intel X86.

Intel 8086 era, internamente, un procesador de 16 bits: todos sus registros tenían 16 bits de ancho. Sin embargo, el bus de direcciones tenía 20 bits de ancho (1 MiB). Esto significaba que no podía mantener una dirección completa en un registro, lo que lo limita a los primeros 64 kB.

La solución de Intel consistió en crear "registros de segmento" de 16 bits cuyo contenido se desplazaría cuatro bits y se agregaría a la dirección. Por ejemplo:

DS ("Data Segment") register: 1234 h DX ("D eXtended") register: + 5678h ------ Actual address read: 179B8h

Esto creó el concepto de segmento 64 kiB. Por lo tanto, un puntero "cercano" sería el contenido del registro DX (5678h), y no sería válido a menos que el registro DS ya estuviera configurado correctamente, mientras que un puntero "lejano" tuviera 32 bits (12345678h, DS seguido de DX) y siempre funcionaría (pero fue más lento ya que tenía que cargar dos registros y luego restaurar el registro DS cuando terminó).

(A medida que las notas de supercama a continuación, una compensación a DX que se desborda "se desborde" antes de agregarse a DS para obtener la dirección final. Esto permitió que las compensaciones de 16 bits accedan a cualquier dirección en el segmento de 64 kB, no solo la parte que ± 32 kB desde donde apuntaba DX, como se hace en otras arquitecturas con direccionamiento de desplazamiento relativo de 16 bits en algunas instrucciones).

Sin embargo, tenga en cuenta que podría tener dos punteros "lejanos" que son valores diferentes pero apuntan a la misma dirección. Por ejemplo, el puntero lejano 100079B8h apunta al mismo lugar que 12345678h. Por lo tanto, la comparación de puntero en punteros lejanos era una operación no válida: los punteros podían diferir, pero aún señalar el mismo lugar.

Aquí fue donde decidí que los Mac (con los procesadores Motorola 68000 en ese momento) no eran tan malos después de todo, así que me perdí grandes punteros. IIRC, solo eran punteros que garantizaban que todos los bits superpuestos en los registros de segmentos eran 0, como en el segundo ejemplo.

Motorola no tenía este problema con su serie de procesadores 6800, ya que estaban limitados a 64 kb. Cuando crearon la arquitectura 68000, pasaron directamente a registros de 32 bits y, por lo tanto, nunca tuvieron necesidad de punteros cercanos, lejanos o enormes. . (En cambio, su problema era que solo los últimos 24 bits de la dirección realmente importaban, por lo que algunos programadores (notoriamente Apple) usarían los 8 bits altos como "indicadores de puntero", causando problemas cuando los buses de direcciones se expandieron a 32 bits (4 GiB) .)

Linus Torvalds solo aguantó hasta el 80386, que ofrecía un "modo protegido" donde las direcciones eran de 32 bits, y los registros de segmento eran la mitad alta de la dirección, y no se necesitaba agregar nada, y escribió Linux desde el principio para usar protegido sólo en modo, sin cosas de segmentos raros, y es por eso que no tiene soporte de puntero cercano y lejano en Linux (y por qué ninguna compañía que diseñe una nueva arquitectura volverá a ellos si quieren soporte de Linux). Y comieron los juglares de Robin, y hubo mucho regocijo. (Hurra...)


En algunas arquitecturas, un puntero que puede apuntar a cada objeto en el sistema será más grande y más lento para trabajar que uno que pueda apuntar a un subconjunto útil de cosas. Muchas personas dieron respuestas relacionadas con la arquitectura x86 de 16 bits. Varios tipos de punteros eran comunes en los sistemas de 16 bits, aunque las distinciones de casi / miedo podrían reaparecer en sistemas de 64 bits, dependiendo de cómo se implementen (no me sorprendería que muchos sistemas de desarrollo vayan a punteros de 64 bits para todo, a pesar del hecho de que en muchos casos será un gran desperdicio).

En muchos programas, es bastante fácil subdividir el uso de memoria en dos categorías: pequeñas cosas que juntas suman una cantidad bastante pequeña de cosas (64K o 4GB) pero se accederá a ellas con frecuencia, y cosas más grandes que pueden sumar cantidades mucho mayores. , pero que no es necesario acceder tan a menudo. Cuando una aplicación necesita trabajar con parte de un objeto en el área de "cosas grandes", copia esa parte en el área de "cosas pequeñas", trabaja con ella y, si es necesario, la vuelve a escribir.

Algunos programadores se quejan de tener que distinguir entre memoria "cercana" y "lejana", pero en muchos casos hacer tales distinciones puede permitir a los compiladores producir código mucho mejor.

(Nota: Incluso en muchos sistemas de 32 bits, se puede acceder directamente a ciertas áreas de la memoria sin instrucciones adicionales, mientras que otras áreas no. Si, por ejemplo, en un 68000 o un ARM, uno mantiene un registro apuntando al almacenamiento de variables globales, será posible cargar directamente cualquier variable dentro de los primeros 32K (68000) o 2K (ARM) de ese registro. La recuperación de una variable almacenada en otro lugar requerirá una instrucción adicional para calcular la dirección. Colocar variables más frecuentemente utilizadas en las regiones preferidas y dejar que el compilador permita una generación de código más eficiente.


En los viejos tiempos, de acuerdo con el manual de Turbo C, un puntero cercano era meramente 16 bits cuando todo su código y datos encajaban en un segmento. Un puntero lejano estaba compuesto de un segmento así como un desplazamiento, pero no se realizó ninguna normalización. Y un gran puntero se normalizó automáticamente. Es posible que dos punteros lejanos indiquen la misma ubicación en la memoria pero que sean diferentes, mientras que los punteros enormes normalizados que apuntan a la misma ubicación de memoria siempre serán iguales.


Esta terminología se usó en arquitecturas de 16 bits.

En sistemas de 16 bits, los datos se dividieron en segmentos de 64 Kb. Cada módulo cargable (archivo de programa, biblioteca cargada dinámicamente, etc.) tenía un segmento de datos asociado, que podía almacenar hasta 64 Kb de datos solamente.

Un puntero NEAR era un puntero con almacenamiento de 16 bits, y se refería a los datos (solo) en el segmento de datos de los módulos actuales.

Los programas de 16 bits que tenían más de 64 Kb de datos como requisito podían acceder a asignadores especiales que devolverían un puntero FAR, que era un id. De segmento de datos en los 16 bits superiores y un puntero en ese segmento de datos, en los 16 bits inferiores.

Sin embargo, los programas más grandes querrían tratar con más de 64 Kb de datos contiguos. Un puntero ENORME se ve exactamente como un puntero lejano, tiene 32 bits de almacenamiento, pero el asignador se ha ocupado de organizar una gama de segmentos de datos con identificadores consecutivos, de modo que simplemente incrementando el selector de segmentos de datos el siguiente fragmento de datos de 64 Kb puede ser alcanzado.

Los estándares subyacentes del lenguaje C y C ++ nunca reconocieron oficialmente estos conceptos en sus modelos de memoria: se supone que todos los punteros en un programa C o C ++ son del mismo tamaño. Por lo tanto, los atributos NEAR, FAR y HUGE fueron extensiones proporcionadas por los diversos proveedores de compiladores.


Todas las cosas en esta respuesta son relevantes solo para el antiguo modelo de memoria segmentada 8086 y 80286.

near: un puntero de 16 bits que puede direccionar cualquier byte en un segmento de 64k

ahora: un puntero de 32 bits que contiene un segmento y un desplazamiento. Tenga en cuenta que, dado que los segmentos se pueden superponer, dos punteros lejanos pueden señalar la misma dirección.

enorme: un puntero de 32 bits en el que el segmento está "normalizado" para que dos punteros lejanos apunten a la misma dirección a menos que tengan el mismo valor.

tee: una bebida con mermelada y pan.

Eso nos llevará de vuelta a doh oh oh oh

y cuando se usan estos punteros?

en la década de 1980 y 90 ''hasta 32 bits Windows se hizo omnipresente,


Diferencia entre punteros lejanos y enormes:

Como sabemos por defecto, los punteros están near por ejemplo: int *p es un puntero near . El tamaño del puntero near es de 2 bytes en el caso del compilador de 16 bits. Y ya sabemos muy bien que el tamaño varía de compilador a compilador; solo almacenan el desplazamiento de la dirección del puntero al que hace referencia. Una dirección que consta de solo un desplazamiento tiene un rango de 0 a 64 k bytes.

Punteros Far y huge :

Far punteros Far y huge tienen un tamaño de 4 bytes. Almacenan el segmento y el desplazamiento de la dirección a la que hace referencia el puntero. Entonces, ¿cuál es la diferencia entre ellos?

Limitación de puntero lejano:

No podemos cambiar o modificar la dirección de segmento de una dirección lejana dada aplicando una operación aritmética sobre ella. Es decir, mediante el uso de operador aritmético no podemos saltar de un segmento a otro segmento.

Si va a incrementar la dirección más allá del valor máximo de su dirección de desplazamiento en lugar de incrementar la dirección del segmento, repetirá su dirección de desplazamiento en orden cíclico. Esto también se denomina envoltura, es decir, si la compensación es 0xffff y agregamos 1, entonces es 0x0000 y, de manera similar, si disminuimos 0x0000 por 1, entonces es 0xffff y recuerde que no hay cambios en el segmento.

Ahora voy a comparar enormes y lejanos punteros:

1. Cuando un puntero lejano se incrementa o decrementa SÓLO el desplazamiento del puntero se incrementa o disminuye, pero en el caso del puntero grande, tanto el segmento como el valor de desplazamiento cambiarán.

Considere el siguiente ejemplo, tomado de HERE :

int main() { char far* f=(char far*)0x0000ffff; printf("%Fp",f+0x1); return 0; }

entonces la salida es:

0000:0000

No hay cambio en el valor del segmento.

Y en caso de grandes Punteros:

int main() { char huge* h=(char huge*)0x0000000f; printf("%Fp",h+0x1); return 0; }

La salida es:

0001:0000

Esto se debe a la operación de incremento que no solo cambia el valor de compensación sino también el valor de segmento. Esto significa que el segmento no cambiará en el caso de punteros far pero en el caso de huge puntero huge , puede moverse de un segmento a otro.

2. Cuando se usan operadores relacionales en punteros lejanos, solo se comparan los desplazamientos. En otras palabras, los operadores relacionales solo trabajarán en punteros lejanos si los valores de segmento de los punteros que se comparan son los mismos. Y en caso de que esto no ocurra, en realidad se produce una comparación de direcciones absolutas. Comprendamos con la ayuda de un ejemplo de puntero far :

int main() { char far * p=(char far*)0x12340001; char far* p1=(char far*)0x12300041; if(p==p1) printf("same"); else printf("different"); return 0; }

Salida:

different

En huge puntero:

int main() { char huge * p=(char huge*)0x12340001; char huge* p1=(char huge*)0x12300041; if(p==p1) printf("same"); else printf("different"); return 0; }

Salida:

same

Explicación: Como vemos, la dirección absoluta para p y p1 es 12341 ( 1234*10+1 o 1230*10+41 ) pero no se consideran iguales en el primer caso porque en el caso de los punteros far solo se comparan las compensaciones, es decir, verificar si 0001==0041 . Que es falso

Y en el caso de los punteros grandes, la operación de comparación se realiza en direcciones absolutas que son iguales.

  1. Un puntero lejano nunca se normaliza, pero se normaliza un puntero huge . Un puntero normalizado es aquel que tiene la mayor cantidad de dirección posible en el segmento, lo que significa que el desplazamiento nunca es mayor que 15.

    supongamos que si tenemos 0x1234:1234 entonces la forma normalizada de la misma es 0x1357:0004 (la dirección absoluta es 13574 ). Un puntero grande se normaliza solo cuando se realiza una operación aritmética y no se normaliza durante la asignación.

    int main() { char huge* h=(char huge*)0x12341234; char huge* h1=(char huge*)0x12341234; printf("h=%Fp/nh1=%Fp",h,h1+0x1); return 0; }

    Salida:

    h=1234:1234 h1=1357:0005

    Explicación: el puntero huge no se normaliza en caso de asignación. Pero si se realiza una operación aritmética en él, se normalizará. Entonces, h es 1234:1234 y h1 es 1357:0005 que está normalizado.

    4.El desplazamiento del puntero grande es menor a 16 debido a la normalización y no es así en el caso de punteros lejanos.

    tomemos un ejemplo para entender lo que quiero decir:

    int main() { char far* f=(char far*)0x0000000f; printf("%Fp",f+0x1); return 0; }

Salida:

0000:0010

En caso de huge puntero:

int main() { char huge* h=(char huge*)0x0000000f; printf("%Fp",h+0x1); return 0; } Output: 0001:0000

Explicación: a medida que incrementemos el puntero lejano en 1, será 0000:0010 Y a medida que incrementemos el puntero grande en 1, entonces será 0001:0000 porque su desplazamiento no puede ser mayor que 15; en otras palabras, se normalizará.