c++ - sirve - punteros y matrices en c
¿Cuál es la razón para las limitaciones en aritmética o comparación de punteros? (8)
(Por favor, no upvote) Este es un resumen de la respuesta, y la razón por la que elegí la respuesta de Mark Ransom, y para hacer esta publicación: la respuesta es su publicación + su siguiente comentario.
La pregunta era ¿Cuáles son las razones de tales limitaciones (que la aritmética y la comparación de punteros deben recaer en un objeto completo único)?
El comentario de Ransom + resumen de la respuesta: dado que no existe un concepto de espacio de direcciones, la única posibilidad de restringir la aritmética de punteros para que caiga dentro de un espacio de direcciones era restringirlo en un objeto.
El comentario de Krazy Glew también proporciona una respuesta orientada a la sociología.
En C / C ++, la suma o resta en el puntero se define solo si el puntero resultante se encuentra dentro del objeto completo puntiagudo original. Además, la comparison de dos punteros solo se puede realizar si los dos objetos puntiagudos son subobjetos de un objeto completo único.
¿Cuáles son las razones de tales limitaciones?
Supuse que el modelo de memoria segmentada (ver here §1.2.1) podría ser una de las razones, pero como los compiladores pueden definir un orden total en todos los indicadores, como lo demuestra esta answer , lo dudo.
Dado que el estándar C pretende cubrir la mayoría de las arquitecturas de procesadores, también debería cubrir esta: Imagine una arquitectura (conozco una, pero no la nombraría) donde los punteros no son solo números simples, sino estructuras o "descriptores". ". Dicha estructura contiene información sobre el objeto al que apunta (su dirección virtual y tamaño) y el desplazamiento dentro de él. Agregar o restar un puntero produce una nueva estructura con solo el campo de compensación ajustado; producir una estructura con el desplazamiento mayor que el tamaño del objeto está prohibido por hardware. Existen otras restricciones (como la forma en que se produce el descriptor inicial o las otras formas de modificarlo), pero no son relevantes para el tema.
El fundamento es que algunas arquitecturas tienen memoria segmentada, y los punteros a diferentes objetos pueden apuntar a diferentes segmentos de memoria. La diferencia entre los dos punteros entonces no sería necesariamente algo significativo.
Esto se remonta a la C. estándar. La lógica C no menciona esto explícitamente, pero sugiere que esta es la razón, si miramos donde explica la razón por la cual el uso de un índice de matriz negativo es un comportamiento indefinido (C99 justificación 5.10 6.5.6, énfasis mío):
En el caso de p-1, por otro lado, se tendría que asignar un objeto completo antes de la matriz de objetos que p atraviesa, por lo que los bucles de disminución que se ejecutan en la parte inferior de una matriz pueden fallar. Esta restricción permite que las arquitecturas segmentadas, por ejemplo, coloquen objetos al comienzo de un rango de memoria direccionable.
El motivo es mantener la posibilidad de generar código razonable. Esto se aplica a los sistemas con un modelo de memoria plana, así como a los sistemas con modelos de memoria más complejos. Si prohíbe los casos de esquina (no muy útiles) como sumar o restar matrices y exigir un orden total de punteros entre objetos, puede omitir muchos gastos generales en el código generado.
Las limitaciones impuestas por la norma permiten al compilador hacer suposiciones sobre la aritmética de punteros y usar esto para mejorar la calidad del código. Cubre tanto la computación estática del compilador en lugar del tiempo de ejecución como la elección de qué instrucciones y modos de direccionamiento usar. Como ejemplo, considere un programa con dos punteros p1
y p2
. Si el compilador puede deducir que apuntan a diferentes objetos de datos, puede asumir con seguridad que cualquier operación sin base en el siguiente p1
afectará al objeto apuntado por p2
. Esto permite que el compilador reordene las cargas y los almacenes basados en p1
sin considerar las cargas y los almacenes basados en p2
y p2
.
En la mayoría de los casos en los que Stanadrd clasifica una acción como invocando un comportamiento indefinido, lo ha hecho porque:
Puede haber plataformas donde definir el comportamiento sería costoso. Las arquitecturas segmentadas podrían comportarse de manera extraña si el código intenta hacer aritmética de punteros que se extiende más allá de los límites de los objetos, y algunos compiladores pueden evaluar
p > q
probando el signo deqp
.Hay algunos tipos de programación en los que definir el comportamiento sería inútil. Muchos tipos de código pueden funcionar bien sin depender de formas de suma, resta o comparación relacional de punteros más allá de las que proporciona el Estándar.
Las personas que escriben compiladores para diversos propósitos deben ser capaces de reconocer los casos en que los compiladores de calidad destinados a tales fines deben comportarse de manera predecible, y manejar dichos casos cuando sea apropiado, ya sea que el Estándar los obligue a hacerlo.
Tanto el # 1 como el # 2 son barras muy bajas, y el # 3 se pensó que era un "dame". Aunque se ha puesto de moda que los escritores de compiladores muestren su inteligencia al encontrar formas de romper el código cuyo comportamiento fue definido por implementaciones de calidad destinadas a la programación de bajo nivel, no creo que los autores de la Norma esperaran que los escritores de compiladores percibieran una gran diferencia entre las acciones que se requerían para comportarse de manera predecible, en comparación con aquellas donde se esperaba que casi todas las implementaciones de calidad se comportaran de manera idéntica, pero donde podría ser útil permitir que algunas implementaciones arcanas hagan otra cosa.
Existen arquitecturas en las que los espacios de programa y de datos están separados, y es simplemente imposible restar dos punteros arbitrarios. Un puntero a una función o a datos constantes estará en un espacio de direcciones completamente diferente a una variable normal.
Incluso si proporcionó arbitrariamente una clasificación entre diferentes espacios de direcciones, existe la posibilidad de que el tipo diff_t
tenga que ser de mayor tamaño. Y el proceso de comparar o restar dos punteros sería enormemente complicado. Esa es una mala idea en un lenguaje que está diseñado para la velocidad.
Me gustaría responder a esto invirtiendo la pregunta. En lugar de preguntar por qué la adición de punteros y la mayoría de las operaciones aritméticas no están permitidas, ¿por qué los punteros solo permiten sumar o restar un entero, un incremento post y pre y una disminución y comparación (o resta) de punteros que apuntan a la misma matriz? Tiene que ver con la consecuencia lógica de la operación aritmética. Agregar / restar un entero n a un puntero p me da la dirección del elemento nth del elemento apuntado actualmente, ya sea en la dirección hacia adelante o hacia atrás. De manera similar, restar p1 y p2 apuntando a la misma matriz me da el recuento de elementos entre los dos punteros. El hecho (o diseño) de que las operaciones aritméticas de punteros se definen de acuerdo con el tipo de variable al que apunta es un golpe de genio real. Cualquier operación distinta de las permitidas desafía la programación o el razonamiento filosóficamente lógico y, por lo tanto, no está permitida.
Usted solo prueba que la restricción podría eliminarse, pero no comprendió que tendría un costo (en términos de memoria y código), que era contrario a los objetivos de C.
Específicamente, la diferencia debe tener un tipo, que es ptrdiff_t, y uno asumirá que es similar a size_t.
En un modelo de memoria segmentada (normalmente) indirectamente tiene una limitación en el tamaño de los objetos, asumiendo que las respuestas son: ¿Cuál es el tamaño real de los tipos `size_t`,` uintptr_t`, `intptr_t` y` ptrdiff_t` en 16 bits? ¿Sistemas que utilizan el modo de direccionamiento segmentado? son correctos
Por lo tanto, al menos para las diferencias, eliminar esa restricción no solo agregaría instrucciones adicionales para garantizar un orden total, para un caso de esquina sin importancia (como en otra respuesta), sino que también gastará el doble de memoria para las diferencias, etc.
C fue diseñado para ser más minimalista y no para forzar al compilador a gastar memoria y código en tales casos. (En esos días las limitaciones de memoria importaban más.)
Obviamente, también hay otros beneficios, como la posibilidad de detectar errores al mezclar punteros de diferentes arreglos. De manera similar, la mezcla de iteradores para dos contenedores diferentes no está definida en C ++ (con algunas excepciones menores), y algunas implementaciones de depuración detectan dichos errores.