Sé que los punteros tienen direcciones. Sé que los tipos de punteros son "generalmente" conocidos en función del "tipo" de datos que señalan. Pero, los punteros siguen siendo variables y las direcciones que contienen deben tener un "tipo" de datos. Según mi información, las direcciones están en formato hexadecimal. Pero, todavía no sé qué "tipo" de datos es este hexadecimal. (Tenga en cuenta que sé lo que es un hexadecimal, pero cuando dice 10CBA20
, por ejemplo, ¿esta cadena de caracteres es? Enteros? ¿Qué? Cuando quiero acceder a la dirección y manipularla ... en sí misma, necesito saber su tipo. Esto es por eso que estoy preguntando)
30
Respuestas:
El tipo de una variable de puntero es ... puntero.
Las operaciones que se le permiten formalmente en C son compararlo (con otros punteros, o el valor especial NULL / zero), sumar o restar enteros, o lanzarlo a otros punteros.
Una vez que acepte un comportamiento indefinido , puede ver cuál es realmente el valor. Por lo general, será una palabra de máquina, el mismo tipo de cosa que un número entero, y generalmente se puede convertir sin pérdida hacia y desde un tipo entero. (Una gran cantidad de código de Windows hace esto ocultando punteros en DWORD o HANDLE typedefs).
Hay algunas arquitecturas donde los punteros no son simples porque la memoria no es plana. DOS / 8086 'cerca' y 'lejos'; Diferentes espacios de memoria y código de PIC.
fuente
p1-p2
. El resultado es un valor integral firmado. En particular,&(array[i])-&(array[j]) == i-j
intptr_t
yuintptr_t
que se garantiza que son "lo suficientemente grandes" para los valores de puntero.p
especificador a printf hace que obtener una representación legible por humanos de un puntero nulo sea un comportamiento definido, si la implementación depende de c.Estás complicando demasiado las cosas.
Las direcciones son solo enteros, punto. Idealmente, son el número de la celda de memoria referenciada (en la práctica, esto se vuelve más complicado debido a los segmentos, la memoria virtual, etc.).
La sintaxis hexadecimal es una ficción completa que existe solo para la conveniencia de los programadores. 0x1A y 26 son exactamente el mismo número de exactamente el mismo tipo , y tampoco es lo que usa la computadora: internamente, la computadora siempre usa 00011010 (una serie de señales binarias).
Si un compilador le permite o no tratar los punteros como números depende de la definición del lenguaje: los lenguajes de "programación de sistemas" son tradicionalmente más transparentes sobre cómo funcionan las cosas bajo el capó, mientras que los lenguajes de "alto nivel" a menudo intentan ocultar el metal desnudo. del programador, pero eso no cambia nada sobre el hecho de que los punteros son números, y generalmente el tipo de número más común (el que tiene tantos bits como la arquitectura de su procesador).
fuente
Un puntero es solo eso: un puntero. No es otra cosa. No intentes pensar que es otra cosa.
En lenguajes como C, C ++ y Objective-C, los punteros de datos tienen cuatro tipos de valores posibles:
También hay punteros de función, que identifican una función o son punteros de función nula o tienen un valor indeterminado.
Otros punteros son "puntero a miembro" en C ++. ¡Estas definitivamente no son direcciones de memoria! En cambio, identifican a un miembro de cualquier instancia de una clase. En Objective-C, tiene selectores, que son algo así como "puntero a un método de instancia con un nombre de método y nombres de argumento". Al igual que un puntero de miembro, identifica todos los métodos de todas las clases siempre que se vean iguales.
Puede investigar cómo un compilador específico implementa punteros, pero esa es una pregunta completamente diferente.
fuente
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
La variablepmi
no sería muy útil si no contuviera una dirección de memoria, es decir, como establece la última línea del código, la dirección del miembronum
de la instanciaa
de la claseA
. Podría lanzar esto a unint
puntero ordinario (aunque el compilador probablemente le daría una advertencia) y desreferenciarlo con éxito (demostrando que es azúcar sintáctico para cualquier otro puntero).Un puntero es un direccionamiento de patrón de bits (que identifica de manera única con el propósito de leer o escribir) una palabra de almacenamiento en RAM. Por razones históricas y convencionales, la unidad de actualización es de ocho bits, conocida en inglés como "byte" o en francés, más lógicamente, como un octeto. Esto es ubicuo pero no inherente; Otros tamaños han existido.
Si no recuerdo mal, había una computadora que usaba una palabra de 29 bits; no solo no es una potencia de dos, es incluso primo. Pensé que esto era SILLIAC, pero el artículo pertinente de Wikipedia no lo admite. CAN BUS utiliza direcciones de 29 bits pero, por convención, las direcciones de red no se denominan punteros, incluso cuando son funcionalmente idénticas.
La gente sigue afirmando que los punteros son enteros. Esto no es intrínseco ni esencial, pero si interpretamos los patrones de bits como enteros, emerge la calidad útil de la ordinalidad, lo que permite la implementación muy directa (y, por lo tanto, eficiente en hardware pequeño) de construcciones como "string" y "array". La noción de memoria contigua depende de la adyacencia ordinal, y es posible el posicionamiento relativo; La comparación de enteros y las operaciones aritméticas se pueden aplicar de manera significativa. Por esta razón, casi siempre existe una fuerte correlación entre el tamaño de palabra para el direccionamiento de almacenamiento y la ALU (lo que hace matemática entera).
A veces los dos no se corresponden. En las primeras PC, el bus de direcciones tenía 24 bits de ancho.
fuente
char*
ejemplo, para copiar / comparar la memoria, ysizeof char==1
según lo define el estándar C), no palabra (a menos que el tamaño de palabra de la CPU sea el mismo que el tamaño de byte).Básicamente, cada computadora moderna es una máquina de empuje de bits. Por lo general, empuja bits en grupos de datos, llamados bytes, palabras, dwords o qwords.
Un byte consta de 8 bits, una palabra de 2 bytes (o 16 bits), una palabra d de 2 palabras (o 32 bits) y una palabra q de 2 dwords (o 64 bits). Estas no son la única forma de organizar los bits. También se produce manipulación de 128 bits y 256 bits, a menudo en las instrucciones SIMD.
Las instrucciones de ensamblaje operan en registros y las direcciones de memoria generalmente operan en una de las formas anteriores.
Las ALU (unidades lógicas aritméticas) operan en paquetes de bits como si representaran números enteros (generalmente el formato Complemento de dos) y las FPU como si tuvieran valores de coma flotante (generalmente estilo IEEE 754
float
ydouble
). Otras partes actuarán como si fueran datos agrupados de algún formato, caracteres, entradas de tabla, instrucciones de CPU o direcciones.En una computadora típica de 64 bits, los paquetes de 8 bytes (64 bits) son direcciones. Mostramos estas direcciones convencionalmente como en un formato hexadecimal (como
0xabcd1234cdef5678
), pero esa es solo una manera fácil para que los humanos lean los patrones de bits. Cada byte (8 bits) se escribe como dos caracteres hexadecimales (de manera equivalente, cada carácter hexadecimal - 0 a F - representa 4 bits).Lo que realmente está sucediendo (para cierto nivel) es que hay bits, generalmente almacenados en un registro o almacenados en ubicaciones adyacentes en un banco de memoria, y solo estamos tratando de describirlos a otro humano.
Seguir un puntero consiste en pedirle al controlador de memoria que nos brinde algunos datos en esa ubicación. Por lo general, le pediría al controlador de memoria un cierto número de bytes en una ubicación determinada (bueno, implícitamente un rango de ubicaciones, generalmente contiguas), y se entrega a través de varios mecanismos en los que no entraré.
El código generalmente especifica un destino para los datos que se van a buscar (un registro, otra dirección de memoria, etc.) y, por lo general, es una mala idea cargar datos de punto flotante en un registro esperando un número entero, o viceversa.
El tipo de datos en C / C ++ es algo que el compilador realiza un seguimiento y cambia el código que se genera. Por lo general, no hay nada intrínseco en los datos que lo haga realmente de cualquier tipo. Solo una colección de bits (organizados en bytes) que el código manipula de forma entera (o de forma flotante, o de dirección).
Existen excepciones para esto. Hay arquitecturas donde ciertas cosas son un tipo diferente de bits. El ejemplo más común son las páginas de ejecución protegidas: mientras que las instrucciones que le dicen a la CPU qué hacen son bits, en el tiempo de ejecución, las páginas (de memoria) que contienen código para ejecutar están marcadas especialmente, no pueden modificarse y no puede ejecutar páginas que no están marcadas como páginas de ejecución.
También hay datos de solo lectura (a veces almacenados en ROM que no se pueden escribir físicamente), problemas de alineación (algunos procesadores no se pueden cargar
double
s de la memoria a menos que estén alineados de manera particular, o instrucciones SIMD que requieren cierta alineación), y una miríada de Otras peculiaridades de la arquitectura.Incluso el nivel de detalle anterior es una mentira. Las computadoras no están "realmente" presionando bits, realmente están presionando los voltajes y la corriente. Estos voltajes y corrientes a veces no hacen lo que se supone que deben hacer en el nivel de abstracción de bits. Los chips están diseñados para detectar la mayoría de estos errores y corregirlos sin que la abstracción de nivel superior tenga que ser consciente de ello.
Incluso eso es una mentira.
Cada nivel de abstracción oculta el siguiente y le permite pensar en resolver problemas sin tener que tener en cuenta los diagramas de Feynman para imprimir
"Hello World"
.Entonces, con un nivel suficiente de honestidad, las computadoras empujan bits, y esos bits tienen significado por cómo se usan.
fuente
La gente se ha preocupado mucho de si los punteros son enteros o no. En realidad hay respuestas a estas preguntas. Sin embargo, tendrá que dar un paso hacia la tierra de las especificaciones, que no es para los débiles de corazón. Vamos a echar un vistazo a la especificación C, ISO / IEC 9899: TC2
6.3.2.3 Punteros
Un entero puede convertirse a cualquier tipo de puntero. Excepto como se especificó anteriormente, el resultado está definido por la implementación, podría no estar correctamente alineado, podría no apuntar a una entidad del tipo referenciado y podría ser una representación trampa.
Cualquier tipo de puntero se puede convertir a un tipo entero. Excepto como se especificó anteriormente, el resultado está definido por la implementación. Si el resultado no puede representarse en el tipo entero, el comportamiento es indefinido. El resultado no necesita estar en el rango de valores de ningún tipo de entero.
Ahora para esto, necesitará conocer algunos términos de especificaciones comunes. "implementación definida" significa que cada compilador puede definirlo de manera diferente. De hecho, un compilador puede incluso definirlo de diferentes maneras dependiendo de la configuración de su compilador. El comportamiento indefinido significa que el compilador puede hacer absolutamente cualquier cosa, desde dar un error de tiempo de compilación hasta comportamientos inexplicables, hasta trabajar perfectamente.
A partir de esto, podemos ver que el formulario de almacenamiento subyacente no está especificado, aparte de que puede haber una conversión a un tipo entero. Ahora bien, la verdad es que prácticamente todos los compiladores bajo el sol representan punteros debajo del capó como direcciones enteras (con un puñado de casos especiales en los que podría representarse como 2 enteros en lugar de solo 1), pero la especificación permite absolutamente cualquier cosa, como representar direcciones como una cadena de 10 caracteres!
Si avanzamos rápidamente fuera de C y miramos las especificaciones de C ++, obtenemos un poco más de claridad
reinterpret_cast
, pero este es un lenguaje diferente, por lo que su valor para usted puede variar:ISO / IEC N337: especificación de borrador de C ++ 11 (solo tengo el borrador a mano)
5.2.10 Reinterpretar elenco
Un puntero se puede convertir explícitamente a cualquier tipo integral lo suficientemente grande como para contenerlo. La función de mapeo está definida por la implementación. [Nota: no pretende sorprender a quienes conocen la estructura de direccionamiento de la máquina subyacente. —Nota final] Un valor de tipo std :: nullptr_t se puede convertir a un tipo integral; la conversión tiene el mismo significado y validez que una conversión de (void *) 0 al tipo integral. [Nota: un reinterpret_cast no se puede usar para convertir un valor de ningún tipo al tipo std :: nullptr_t. —Nota final]
Un valor de tipo integral o tipo de enumeración se puede convertir explícitamente en un puntero. Un puntero convertido a un entero de tamaño suficiente (si existe alguno en la implementación) y volver al mismo tipo de puntero tendrá su valor original; las asignaciones entre punteros y enteros se definen de otra manera en la implementación. [Nota: Excepto como se describe en 3.7.4.3, el resultado de dicha conversión no será un valor de puntero derivado de forma segura. —Nota final]
Como puede ver aquí, con unos pocos años más en su haber, C ++ descubrió que era seguro suponer que existía un mapeo de enteros, por lo que ya no se habla de comportamiento indefinido (aunque existe una contradicción interesante entre las partes 4 y 5 con la frase "si existe en la implementación")
¿Ahora qué deberías quitar de esto?
La mejor apuesta: lanzar a un (char *). Las especificaciones C y C ++ están llenas de reglas que especifican el empaquetado de matrices y estructuras, y ambas siempre permiten la conversión de cualquier puntero a un char *. char siempre tiene 1 byte (no está garantizado en C, pero en C ++ 11 se ha convertido en una parte obligatoria del lenguaje, por lo que es relativamente seguro asumir que es 1 byte en todas partes). Esto le permite hacer una aritmética de puntero a nivel byte por byte sin tener que recurrir a la necesidad de conocer las representaciones específicas de implementación de los punteros.
fuente
char *
? Estoy pensando en una máquina hipotética con espacios de direcciones separados para código y datos.char
siempre es 1 byte en C. Citando del estándar C: "El operador sizeof produce el tamaño (en bytes) de su operando" y "Cuando sizeof se aplica a un operando que tiene un tipo char, unsigned char o signo char, (o una versión calificada del mismo) el resultado es 1. " Quizás esté pensando que un byte tiene 8 bits de longitud. Ese no es necesariamente el caso. Para cumplir con el estándar, un byte debe contener al menos 8 bits.En la mayoría de las arquitecturas, el tipo de puntero deja de existir una vez que se ha traducido al código de máquina (a excepción de quizás "punteros gordos"). Por lo tanto, un puntero a un
int
sería indistinguible de un puntero a undouble
, al menos por sí solo. *[*] Aunque, aún puedes hacer conjeturas en función del tipo de operaciones que le apliques.
fuente
Una cosa importante para entender sobre C y C ++ es qué tipos son en realidad. Todo lo que realmente hacen es indicarle al compilador cómo interpretar un conjunto de bits / bytes. Comencemos con el siguiente código:
Dependiendo de la arquitectura, un entero generalmente recibe 32 bits de espacio para almacenar ese valor. Eso significa que el espacio en la memoria donde se almacena var se verá como "11111111 11111111 11111010 11000111" o en hexadecimal "0xFFFFFAC7". Eso es. Eso es todo lo que se almacena en esa ubicación. Todo lo que hacen es decirle al compilador cómo interpretar esa información. Los punteros no son diferentes. Si hago algo como esto:
Luego, el compilador obtendrá la ubicación de var, y luego almacenará esa dirección de la misma manera que el primer fragmento de código guarda el valor -1337. No hay diferencia en cómo se almacenan, solo en cómo se usan. Ni siquiera importa que haya hecho var_ptr un puntero a un int. Si quisieras, podrías hacerlo.
Esto copiará el valor hexadecimal anterior de var (0xFFFFFAC7) en la ubicación que almacena el valor de var2. Si tuviéramos que usar var2, encontraríamos que el valor sería 4294965959. Los bytes en var2 son los mismos que var, pero el valor numérico difiere. El compilador los interpretó de manera diferente porque le dijimos que esos bits representan un largo sin signo. También podría hacer lo mismo para el valor del puntero.
Terminaría interpretando el valor que representa la dirección de var como un int sin signo en este ejemplo.
Esperemos que esto te aclare las cosas y te dé una mejor idea de cómo funciona C. Tenga en cuenta que NO DEBE hacer ninguna de las cosas locas que hice en las dos líneas siguientes en el código de producción real. Eso fue solo para demostración.
fuente
Entero.
El espacio de direcciones en una computadora se numera secuencialmente, comenzando en 0, y se incrementa en 1. Por lo tanto, un puntero contendrá un número entero que corresponde a una dirección en el espacio de direcciones.
fuente
Los tipos se combinan.
En particular, ciertos tipos se combinan, casi como si estuvieran parametrizados con marcadores de posición. Los tipos de matriz y puntero son así; tienen uno de esos marcadores de posición, que es el tipo del elemento de la matriz o la cosa a la que se apunta, respectivamente. Los tipos de funciones también son así; pueden tener múltiples marcadores de posición para los parámetros y un marcador de posición para el tipo de retorno.
Una variable que se declara que contiene un puntero a char tiene el tipo "puntero a char". Una variable que se declara que contiene un puntero a puntero a int tiene el tipo "puntero a puntero a int".
Un (valor de) tipo "puntero a puntero a int" se puede cambiar a "puntero a int" mediante una operación de desreferencia. Entonces, la noción de tipo no es solo palabras, sino una construcción matemáticamente significativa, que dicta lo que podemos hacer con los valores del tipo (como desreferenciar, pasar como parámetro o asignar a variable; también determina el tamaño (conteo de bytes) de operaciones de indexación, aritmética e incremento / decremento).
PD: si quieres profundizar en los tipos, prueba este blog: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/
fuente