En K&R (The C Programming Language 2nd Edition) capítulo 5 leí lo siguiente:
Primero, los punteros pueden compararse bajo ciertas circunstancias. Si
p
yq
punto a los miembros de la misma matriz, entonces, como las relaciones==
,!=
,<
,>=
, etc trabajo correctamente.
Lo que parece implicar que solo se pueden comparar los punteros que apuntan a la misma matriz.
Sin embargo, cuando probé este código
char t = 't';
char *pt = &t;
char x = 'x';
char *px = &x;
printf("%d\n", pt > px);
1
se imprime en la pantalla.
En primer lugar, pensé que quedaría indefinido o algún tipo o error, porque pt
y px
no estoy apuntando a la misma matriz (al menos en mi entendimiento).
También se pt > px
debe a que ambos punteros apuntan a variables almacenadas en la pila, y la pila crece, por lo que la dirección de memoria de t
es mayor que la de x
? ¿Por qué pt > px
es verdad?
Me confundo más cuando aparece Malloc. También en K&R en el capítulo 8.7 está escrito lo siguiente:
Sin embargo, todavía existe una suposición de que los punteros a diferentes bloques devueltos por
sbrk
se pueden comparar significativamente. Esto no está garantizado por el estándar que permite comparaciones de punteros solo dentro de una matriz. Por lo tanto, esta versión demalloc
es portátil solo entre máquinas para las cuales la comparación general de punteros es significativa.
No tuve problema en comparar punteros que apuntaban al espacio mal colocado en el montón con punteros que apuntaban a variables de pila.
Por ejemplo, el siguiente código funcionó bien, al 1
imprimirse:
char t = 't';
char *pt = &t;
char *px = malloc(10);
strcpy(px, pt);
printf("%d\n", pt > px);
Basado en mis experimentos con mi compilador, me llevan a pensar que cualquier puntero se puede comparar con cualquier otro puntero, independientemente de dónde apunten individualmente. Además, creo que la aritmética de puntero entre dos punteros está bien, sin importar dónde apunten individualmente porque la aritmética solo usa las direcciones de memoria que almacenan los punteros.
Aún así, estoy confundido por lo que estoy leyendo en K&R.
La razón por la que pregunto es porque mi prof. en realidad lo hizo una pregunta de examen. Dio el siguiente código:
struct A { char *p0; char *p1; }; int main(int argc, char **argv) { char a = 0; char *b = "W"; char c[] = [ 'L', 'O', 'L', 0 ]; struct A p[3]; p[0].p0 = &a; p[1].p0 = b; p[2].p0 = c; for(int i = 0; i < 3; i++) { p[i].p1 = malloc(10); strcpy(p[i].p1, p[i].p0); } }
¿Qué evalúan estos para:
p[0].p0 < p[0].p1
p[1].p0 < p[1].p1
p[2].p0 < p[2].p1
La respuesta es 0
, 1
y 0
.
(Mi profesor incluye el descargo de responsabilidad en el examen de que las preguntas son para un entorno de programación Ubuntu Linux 16.04, versión de 64 bits)
(Nota del editor: si SO permitiera más etiquetas, esa última parte garantizaría x86-64 , Linux y tal vez ensamblaje . Si el punto de la pregunta / clase era específicamente detalles de implementación de SO de bajo nivel, en lugar de C. portátil).
C
con lo que es seguro enC
. Sin embargo, siempre se puede comparar dos punteros con el mismo tipo (verificando la igualdad, por ejemplo), utilizando la aritmética de punteros y la comparación,>
y<
solo es seguro cuando se usa dentro de una matriz dada (o bloque de memoria).Respuestas:
De acuerdo con el estándar C11 , los operadores relacionales
<
,<=
,>
, y>=
sólo pueden ser utilizados en los punteros a los elementos de la misma matriz o un objeto struct. Esto se explica en la sección 6.5.8p5:Tenga en cuenta que cualquier comparación que no cumpla con este requisito invoca un comportamiento indefinido , lo que significa (entre otras cosas) que no puede depender de que los resultados sean repetibles.
En su caso particular, tanto para la comparación entre las direcciones de dos variables locales como entre la dirección de una dirección local y una dinámica, la operación parecía "funcionar", sin embargo, el resultado podría cambiar al hacer un cambio aparentemente no relacionado a su código o incluso compilar el mismo código con diferentes configuraciones de optimización. Con un comportamiento indefinido, solo porque el código podría fallar o generar un error no significa que lo hará .
Como ejemplo, un procesador x86 que se ejecuta en modo real 8086 tiene un modelo de memoria segmentado que utiliza un segmento de 16 bits y un desplazamiento de 16 bits para construir una dirección de 20 bits. Entonces, en este caso, una dirección no se convierte exactamente en un número entero.
Los operadores de igualdad
==
y!=
sin embargo no tienen esta restricción. Se pueden usar entre dos punteros a tipos compatibles o punteros NULL. Por lo tanto, usar==
o!=
en ambos ejemplos produciría un código C válido.Sin embargo, incluso con
==
y!=
podría obtener algunos resultados inesperados pero bien definidos. Consulte ¿Puede una comparación de igualdad de punteros no relacionados evaluar a verdadero? para más detalles sobre estoCon respecto a la pregunta del examen dada por su profesor, hace una serie de suposiciones erróneas:
Si tuviera que ejecutar este código en una arquitectura y / o con un compilador que no satisfaga estos supuestos, podría obtener resultados muy diferentes.
Además, ambos ejemplos también exhiben un comportamiento indefinido cuando llaman
strcpy
, ya que el operando correcto (en algunos casos) apunta a un solo carácter y no a una cadena terminada en nulo, lo que hace que la función lea más allá de los límites de la variable dada.fuente
<
entre elmalloc
resultado y una variable local (almacenamiento automático, es decir, pila), podría suponer que la ruta de ejecución nunca se toma y simplemente compila toda la función a unaud2
instrucción (plantea un ilegal -excepción de instrucción que manejará el núcleo al entregar una SIGILL al proceso). GCC / clang hace esto en la práctica para otros tipos de UB, como caerse del final de una novoid
función. godbolt.org está caído en este momento parece, pero intente copiar / pegarint foo(){int x=2;}
y tenga en cuenta la falta de unret
malloc
utiliza para obtener más memoria del sistema operativo, por lo que no hay razón para suponer que sus vars locales (pila de subprocesos) están por encimamalloc
de la asignación dinámica almacenamiento.int x,y;
, una implementación ...El problema principal al comparar punteros con dos matrices distintas del mismo tipo es que las matrices mismas no necesitan colocarse en una posición relativa particular: una podría terminar antes y después de la otra.
No, el resultado depende de la implementación y otros factores impredecibles.
No hay necesariamente una pila . Cuando existe, no necesita crecer hacia abajo. Podría crecer Podría ser no contiguo de alguna manera extraña.
Veamos la especificación C , §6.5.8 en la página 85 que analiza los operadores relacionales (es decir, los operadores de comparación que está utilizando). Tenga en cuenta que esto no se aplica a directa
!=
o==
comparación.La última oración es importante. Si bien reduzco algunos casos no relacionados para ahorrar espacio, hay un caso que es importante para nosotros: dos matrices, que no forman parte de la misma estructura / objeto agregado 1 , y estamos comparando punteros con esas dos matrices. Este es un comportamiento indefinido .
Si bien su compilador acaba de insertar algún tipo de instrucción de máquina CMP (comparar) que compara numéricamente los punteros, y tuvo suerte aquí, UB es una bestia bastante peligrosa. Literalmente, puede suceder cualquier cosa: su compilador podría optimizar toda la función, incluidos los efectos secundarios visibles. Podría engendrar demonios nasales.
1 Se pueden comparar los punteros en dos matrices diferentes que forman parte de la misma estructura, ya que esto se incluye en la cláusula donde las dos matrices son parte del mismo objeto agregado (la estructura).
fuente
t
yx
siendo definido en la misma función, no hay razón para suponer nada acerca de cómo un compilador dirigido a x86-64 presentará locales en el marco de la pila para esta función. La pila que crece hacia abajo no tiene nada que ver con el orden de declaración de variables en una función. Incluso en funciones separadas, si una podría alinearse con la otra, los locales de la función "secundaria" aún podrían mezclarse con los padres.void
función) g ++ y sonido metálico ++ realmente hacer eso en la práctica: godbolt.org/z/g5vesB que suponga que el camino de ejecución no se toma porque conduce a UB, y compile dichos bloques básicos para una instrucción ilegal. O sin instrucciones, simplemente cayendo silenciosamente a cualquier asm que viene después si esa función alguna vez fue llamada. (Por alguna razóngcc
no hace esto, solog++
).Estas preguntas se reducen a:
Y la respuesta a las tres es "implementación definida". Las preguntas de tu profesor son falsas; Lo han basado en el diseño tradicional de Unix:
pero varias unidades modernas (y sistemas alternativos) no se ajustan a esas tradiciones. A menos que hayan precedido la pregunta con "a partir de 1992"; asegúrese de dar un -1 en la evaluación.
fuente
arr[]
es un objeto así, el Estándar exigiría unaarr+32768
comparación mayor quearr
incluso si una comparación de puntero firmada informara lo contrario.En casi cualquier plataforma remotamente moderna, los punteros y los enteros tienen una relación de ordenamiento isomorfo, y los punteros a objetos disjuntos no están intercalados. La mayoría de los compiladores exponen este orden a los programadores cuando las optimizaciones están deshabilitadas, pero el Estándar no hace distinción entre las plataformas que tienen ese orden y las que no lo hacen y no requiere que ninguna implementación exponga tal orden al programador incluso en plataformas que lo harían. definirlo En consecuencia, algunos escritores de compiladores realizan varios tipos de optimizaciones y "optimizaciones" basadas en la suposición de que el código nunca comparará el uso de operadores relacionales en punteros con diferentes objetos.
Según la justificación publicada, los autores del Estándar pretendían que las implementaciones extiendan el lenguaje al especificar cómo se comportarán en situaciones que el Estándar caracteriza como "Comportamiento indefinido" (es decir, donde el Estándar no impone requisitos ) cuando hacerlo sería útil y práctico. , pero algunos escritores de compiladores preferirían asumir que los programas nunca intentarán beneficiarse de algo más allá de lo que exige el Estándar, que permitir que los programas exploten de manera útil los comportamientos que las plataformas podrían soportar sin costo adicional.
No conozco ningún compilador diseñado comercialmente que haga algo extraño con las comparaciones de punteros, pero a medida que los compiladores se trasladan al LLVM no comercial para su back-end, es cada vez más probable que procesen código sin sentido cuyo comportamiento había sido especificado anteriormente compiladores para sus plataformas. Tal comportamiento no se limita a operadores relacionales, sino que incluso puede afectar la igualdad / desigualdad. Por ejemplo, a pesar de que el Estándar especifica que una comparación entre un puntero a un objeto y un puntero "justo pasado" a un objeto inmediatamente anterior comparará los compiladores basados en gcc y LLVM que son propensos a generar código sin sentido si los programas realizan tal comparaciones
Como ejemplo de una situación en la que incluso la comparación de igualdad se comporta sin sentido en gcc y clang, considere:
Tanto clang como gcc generarán código que siempre devolverá 4 incluso si
x
son diez elementos,y
inmediatamente lo sigue yi
es cero, lo que da como resultado que la comparación sea verdadera yp[0]
se escriba con el valor 1. Creo que lo que sucede es que una pasada de optimización se reescribe la función como si*p = 1;
fuera reemplazada porx[10] = 1;
. El último código sería equivalente si el compilador lo interpretara*(x+10)
como equivalente*(y+i)
, pero desafortunadamente una etapa de optimizaciónx[10]
posterior reconoce que un acceso a solo se definiría six
tuviera al menos 11 elementos, lo que haría imposible que ese acceso se vea afectadoy
.Si los compiladores pueden obtener esa "creatividad" con el escenario de igualdad de puntero que describe el Estándar, no confiaría en que se abstengan de ser aún más creativos en los casos en que el Estándar no imponga requisitos.
fuente
Es simple: comparar punteros no tiene sentido ya que nunca se garantiza que las ubicaciones de memoria para los objetos estén en el mismo orden en que las declaró. La excepción son las matrices. & array [0] es menor que & array [1]. Eso es lo que K&R señala. En la práctica, las direcciones de los miembros de struct también están en el orden en que las declara en mi experiencia. No hay garantías sobre eso ... Otra excepción es si compara un puntero por igual. Cuando un puntero es igual a otro, sabes que está apuntando al mismo objeto. Lo que sea que es. Mala pregunta de examen si me preguntas. Dependiendo de Ubuntu Linux 16.04, entorno de programación de la versión de 64 bits para una pregunta de examen? De Verdad ?
fuente
arr[0]
,arr[1]
etc por separado. Usted declaraarr
como un todo, por lo que el orden de los elementos de la matriz individual es un problema diferente al descrito en esta pregunta.memcpy
para copiar una parte contigua de una estructura y afectar a todos los elementos de la misma y no afectar a nada más. El estándar es descuidado sobre la terminología en cuanto a qué tipos de aritmética de puntero se pueden hacer con estructuras omalloc()
almacenamiento asignado. Laoffsetof
macro sería bastante inútil si no se pudiera utilizar el mismo tipo de aritmética de puntero con los bytes de una estructura que con achar[]
, pero el Estándar no dice expresamente que los bytes de una estructura son (o pueden usarse como) Un objeto de matriz.¡Qué pregunta tan provocativa!
Incluso el escaneo superficial de las respuestas y comentarios en este hilo revelará cuán emotiva resulta ser su consulta aparentemente simple y directa.
No debería ser sorprendente.
Indiscutiblemente, los malentendidos sobre el concepto y el uso de punteros representan una causa predominante de fallas graves en la programación en general.
El reconocimiento de esta realidad es evidente en la ubicuidad de los idiomas diseñados específicamente para abordar, y preferiblemente para evitar los desafíos que los punteros presentan por completo. Piense en C ++ y otros derivados de C, Java y sus relaciones, Python y otros scripts, simplemente como los más prominentes y prevalentes, y más o menos ordenados en severidad al tratar el problema.
Desarrollar una comprensión más profunda de los principios subyacentes, por lo tanto, debe ser pertinente para cada individuo que aspira a la excelencia en la programación, especialmente a nivel de sistemas .
Me imagino que esto es precisamente lo que tu maestro quiere demostrar.
Y la naturaleza de C lo convierte en un vehículo conveniente para esta exploración. Menos claro que el ensamblaje, aunque quizás más fácilmente comprensible, y aún mucho más explícitamente que los lenguajes basados en una abstracción más profunda del entorno de ejecución.
Diseñado para facilitar la traducción determinista de la intención del programador en instrucciones que las máquinas pueden comprender, C es un lenguaje de nivel de sistema . Si bien se clasifica como de alto nivel, realmente pertenece a una categoría 'mediana'; pero como no existe ninguno, la designación de 'sistema' tiene que ser suficiente.
Esta característica es en gran parte responsable de convertirlo en un idioma de elección para los controladores de dispositivos , el código del sistema operativo y las implementaciones integradas . Además, una alternativa merecidamente favorecida en aplicaciones donde la eficiencia óptima es primordial; donde eso significa la diferencia entre supervivencia y extinción, y por lo tanto es una necesidad en lugar de un lujo. En tales casos, la conveniencia atractiva de la portabilidad pierde todo su atractivo, y optar por el rendimiento sin brillo del mínimo común denominador se convierte en una opción impensablemente perjudicial .
Lo que hace que C, y algunos de sus derivados, sean bastante especiales, es que permite a sus usuarios un control total , cuando eso es lo que desean, sin imponerles las responsabilidades relacionadas cuando no lo hacen. Sin embargo, nunca ofrece más que los aislamientos más delgados de la máquina , por lo que el uso adecuado exige una comprensión precisa del concepto de punteros .
En esencia, la respuesta a su pregunta es sublimemente simple y satisfactoriamente dulce, en confirmación de sus sospechas. Siempre que , sin embargo, se atribuya la importancia necesaria a cada concepto en esta declaración:
El primero es tanto siempre segura y potencialmente adecuado , mientras que el segundo tan sólo puede ser adecuada cuando ha sido establecida como segura . Sorprendentemente , para algunos, entonces establecer la validez de este último depende y exige lo primero.
Por supuesto, parte de la confusión surge del efecto de la recursividad inherentemente presente dentro del principio de un puntero, y los desafíos que se presentan al diferenciar el contenido de la dirección.
Has supuesto correctamente ,
Y varios contribuyentes han afirmado: los punteros son solo números. A veces algo más cercano a los números complejos , pero todavía no más que los números.
La acritud divertida en la que se ha recibido esta afirmación aquí revela más sobre la naturaleza humana que la programación, pero sigue siendo digna de mención y elaboración. Quizás lo hagamos más tarde ...
Como un comentario comienza a insinuar; Toda esta confusión y consternación deriva de la necesidad de discernir lo que es válido de lo que es seguro , pero eso es una simplificación excesiva. También debemos distinguir qué es funcional y qué es confiable , qué es práctico y qué puede ser apropiado , y aún más: lo que es apropiado en una circunstancia particular de lo que puede ser apropiado en un sentido más general . Por no mencionar; La diferencia entre conformidad y propiedad .
Con ese fin, en primer lugar hay que apreciar precisamente lo que un puntero es .
Como varios han señalado: el término puntero es simplemente un nombre especial para lo que es simplemente un índice y, por lo tanto, nada más que cualquier otro número .
Esto ya debería ser evidente teniendo en cuenta el hecho de que todas las computadoras convencionales contemporáneas son máquinas binarias que necesariamente funcionan exclusivamente con y sobre números . La computación cuántica puede cambiar eso, pero es muy poco probable y no ha alcanzado la mayoría de edad.
Técnicamente, como ha notado, los punteros son direcciones más precisas ; Una idea obvia que introduce naturalmente la gratificante analogía de correlacionarlos con las 'direcciones' de casas o parcelas en una calle.
En un modelo de memoria plana : toda la memoria del sistema está organizada en una sola secuencia lineal: todas las casas de la ciudad se encuentran en la misma carretera, y cada casa se identifica de manera única por su número. Deliciosamente simple.
En esquemas segmentados : se introduce una organización jerárquica de carreteras numeradas por encima de las casas numeradas para que se requieran direcciones compuestas.
Llevándonos al giro adicional que convierte el enigma en una maraña tan fascinantemente complicada . Arriba, era conveniente sugerir que los punteros son direcciones, en aras de la simplicidad y la claridad. Por supuesto, esto no es correcto. Un puntero no es una dirección; un puntero es una referencia a una dirección , contiene una dirección . Al igual que el sobre tiene una referencia a la casa. Contemplar esto puede llevarlo a vislumbrar lo que se entiende con la sugerencia de recursión contenida en el concepto. Todavía; tenemos pocas palabras y hablamos de las direcciones de referencias a direccionesy tal, pronto detiene la mayoría de los cerebros en una excepción de código de operación no válida . Y en su mayor parte, la intención se obtiene fácilmente del contexto, así que volvamos a la calle.
Los trabajadores postales en esta ciudad imaginaria nuestra son muy parecidos a los que encontramos en el mundo "real". Es probable que nadie sufra un derrame cerebral cuando habla o pregunta acerca de una dirección no válida , pero cada uno de ellos se negará cuando les pida que actúen sobre esa información.
Supongamos que solo hay 20 casas en nuestra singular calle. Además, finja que un alma disléxica o equivocada ha dirigido una carta, una muy importante, al número 71. Ahora, podemos preguntarle a nuestro transportista Frank, si existe esa dirección, y él informará de manera simple y tranquila: no . Incluso podemos esperar que él para estimar hasta qué punto fuera de la calle esta ubicación sería mentir si lo hizo existe: aproximadamente 2,5 veces mayor que el final. Nada de esto le causará exasperación. Sin embargo, si tuviéramos que pedirle que entregue esta carta, o que recoja un artículo de ese lugar, es probable que sea bastante franco sobre su descontento y su negativa a cumplir.
Los punteros son solo direcciones, y las direcciones son solo números.
Verifique el resultado de lo siguiente:
Llámalo a todos los punteros que quieras, válidos o no. Por favor, no publicar sus hallazgos si falla en su plataforma, o su (contemporánea) compilador se queja.
Ahora, debido a que los punteros son simplemente números, es inevitablemente válido compararlos. En cierto sentido, esto es precisamente lo que su maestro está demostrando. Todas las siguientes afirmaciones son perfectamente válidas , ¡y adecuadas! - C, y cuando se compila se ejecutará sin encontrar problemas , aunque ninguno de los punteros necesita inicializarse y los valores que contienen pueden ser indefinidos :
result
explícitamente en aras de la claridad , e imprimiéndolo para obligar al compilador a calcular lo que de otro modo sería un código muerto redundante.Por supuesto, el programa está mal formado cuando aob no está definido (léase: no se inicializó correctamente ) en el punto de prueba, pero eso es completamente irrelevante para esta parte de nuestra discusión. Estos fragmentos, al igual que las siguientes afirmaciones, están garantizados , por el 'estándar', para compilar y ejecutarse sin problemas, a pesar de la validez IN de cualquier puntero involucrado.
Los problemas solo surgen cuando se desreferencia un puntero no válido . Cuando le pedimos a Frank que recoja o entregue en la dirección no válida e inexistente.
Dado cualquier puntero arbitrario:
Si bien esta declaración debe compilar y ejecutar:
... como debe ser esto:
... los dos siguientes, en marcado contraste, aún se compilarán fácilmente, pero fallarán en la ejecución a menos que el puntero sea válido , con lo que aquí solo queremos decir que hace referencia a una dirección a la que se ha otorgado acceso a la presente aplicación :
¿Qué tan sutil es el cambio? La distinción radica en la diferencia entre el valor del puntero, que es la dirección, y el valor de los contenidos: de la casa en ese número. No surge ningún problema hasta que se desreferencia el puntero ; hasta que se intente acceder a la dirección a la que se vincula. Al tratar de entregar o recoger el paquete más allá del tramo de la carretera ...
Por extensión, el mismo principio se aplica necesariamente a ejemplos más complejos, incluida la necesidad antes mencionada de establecer la validez requerida:
La comparación relacional y la aritmética ofrecen una utilidad idéntica a la equivalencia de prueba, y son igualmente válidas, en principio. Sin embargo , lo que significarían los resultados de tal cálculo es un asunto completamente diferente, y precisamente el problema abordado por las citas que incluyó.
En C, una matriz es un búfer contiguo, una serie lineal ininterrumpida de ubicaciones de memoria. La comparación y la aritmética aplicada a los punteros de que las ubicaciones de referencia dentro de una serie tan singular son naturalmente, y obviamente significativas en relación tanto entre sí como con esta 'matriz' (que simplemente se identifica por la base). Precisamente, lo mismo se aplica a cada bloque asignado a través de
malloc
, osbrk
. Debido a que estas relaciones son implícitas , el compilador puede establecer relaciones válidas entre ellas y, por lo tanto, puede estar seguro de que los cálculos proporcionarán las respuestas anticipadas.Realizar gimnasia similar en punteros que hacen referencia a bloques o matrices distintos no ofrece ninguna utilidad inherente y aparente . Más aún, ya que cualquier relación que exista en un momento puede ser invalidada por una reasignación que sigue, en la que es muy probable que cambie, incluso se invierta. En tales casos, el compilador no puede obtener la información necesaria para establecer la confianza que tenía en la situación anterior.
¡Usted , sin embargo, como programador, puede tener tal conocimiento! Y en algunos casos están obligados a explotar eso.
Hay SON , por lo tanto, las circunstancias en las que incluso esto es totalmente VÁLIDO y perfectamente ADECUADO.
De hecho, eso es exactamente lo que
malloc
tiene que hacer internamente cuando llega el momento de intentar fusionar bloques recuperados, en la gran mayoría de las arquitecturas. Lo mismo es cierto para el asignador del sistema operativo, como eso detrássbrk
; si es más obvio , con frecuencia , en entidades más dispares , más críticamente , y relevante también en plataformas donde estomalloc
puede no ser. ¿Y cuántos de esos no están escritos en C?La validez, seguridad y éxito de una acción es inevitablemente la consecuencia del nivel de conocimiento sobre el cual se basa y aplica.
En las citas que ha ofrecido, Kernighan y Ritchie están abordando un tema estrechamente relacionado, pero no obstante separado. Están definiendo las limitaciones del lenguaje y explicando cómo puede explotar las capacidades del compilador para protegerlo al menos al detectar construcciones potencialmente erróneas. Describen las longitudes que puede alcanzar el mecanismo , está diseñado, para ayudarlo en su tarea de programación. El compilador es tu servidor, tú eres el maestro. Sin embargo, un maestro sabio es uno que está íntimamente familiarizado con las capacidades de sus diversos sirvientes.
Dentro de este contexto, el comportamiento indefinido sirve para indicar peligro potencial y la posibilidad de daño; para no implicar una condena inminente e irreversible, o el fin del mundo tal como lo conocemos. Simplemente significa que nosotros - 'es decir, el compilador' - no podemos hacer ninguna conjetura sobre lo que esto puede ser, o representar, y por esta razón elegimos lavarnos las manos al respecto. No seremos responsables por cualquier desventura que pueda resultar del uso o mal uso de esta instalación .
En efecto, simplemente dice: "Más allá de este punto, vaquero : estás solo ..."
Su profesor está tratando de demostrarle los mejores matices .
Observe el gran cuidado que han tomado al elaborar su ejemplo; y cómo quebradizo que todavía es. Al tomar la dirección de
a
, enel compilador se ve obligado a asignar almacenamiento real para la variable, en lugar de colocarlo en un registro. Sin embargo, al ser una variable automática, el programador no tiene control sobre dónde está asignado y, por lo tanto, no puede hacer ninguna conjetura válida sobre lo que le seguiría. Es por eso que
a
debe establecerse igual a cero para que el código funcione como se espera.Simplemente cambiando esta línea:
a esto:
hace que el comportamiento del programa se vuelva indefinido . Como mínimo, la primera respuesta ahora será 1; Pero el problema es mucho más siniestro.
Ahora el código invita al desastre.
Aunque sigue siendo perfectamente válido e incluso se ajusta al estándar , ahora está mal formado y, aunque es seguro que se compila, puede fallar en la ejecución por varios motivos. Por ahora existen múltiples problemas - ninguno de los cuales el compilador es capaz de reconocer.
strcpy
comenzará en la dirección dea
, y continuará más allá de esto para consumir - y transferir - byte tras byte, hasta que encuentre un valor nulo.El
p1
puntero se ha inicializado en un bloque de exactamente 10 bytes.Si
a
se coloca al final de un bloque y el proceso no tiene acceso a lo que sigue, la siguiente lectura, de p0 [1], provocará una segfault. Este escenario es poco probable en la arquitectura x86, pero es posible.Si
a
se puede acceder al área más allá de la dirección de , no se producirá ningún error de lectura, pero el programa aún no se salva de la desgracia.Si ocurre un byte cero dentro de los diez que comienzan en la dirección de
a
, aún puede sobrevivir, ya que entoncesstrcpy
se detendrá y al menos no sufriremos una violación de escritura.Si se no criticada por leer mal, pero no hay byte cero se produce en este lapso de 10,
strcpy
continuará e intentar escribir más allá del bloque asignado pormalloc
.Si esta área no es propiedad del proceso, la segfault debe activarse inmediatamente.
La situación aún más desastrosa, y sutil , surge cuando el siguiente bloque es propiedad del proceso, ya que entonces el error no se puede detectar, no se puede generar ninguna señal y, por lo tanto, puede "parecer" que todavía "funciona" , mientras que en realidad sobrescribirá otros datos, las estructuras de administración de su asignador o incluso el código (en ciertos entornos operativos).
Esta es la razón por la cual los errores relacionados con el puntero pueden ser tan difíciles de rastrear . Imagine estas líneas enterradas en lo profundo de miles de líneas de código intrincadamente relacionado, que alguien más ha escrito, y se le indica que profundice.
Sin embargo , el programa debe todavía compilar, ya que sigue siendo perfectamente válido y conformes estándar C.
Este tipo de errores, ningún estándar y ningún compilador pueden proteger a los incautos. Me imagino que eso es exactamente lo que pretenden enseñarte.
Las personas paranoicas constantemente buscan cambiar la naturaleza de C para deshacerse de estas posibilidades problemáticas y así salvarnos de nosotros mismos; Pero eso es falso . Esta es la responsabilidad que estamos obligados a aceptar cuando elegimos perseguir el poder y obtener la libertad que nos ofrece un control más directo e integral de la máquina. Los promotores y perseguidores de la perfección en el rendimiento nunca aceptarán nada menos.
La portabilidad y la generalidad que representa es una consideración fundamentalmente separada y todo lo que el estándar busca abordar:
Es por eso que es perfectamente apropiado mantenerlo distinto de la definición y especificación técnica del lenguaje en sí. Contrariamente a lo que muchos creen que la generalidad es antitética a excepcional y ejemplar .
Para concluir:
Si esto no fuera cierto, la programación tal como la conocemos , y nos encanta, no hubiera sido posible.
fuente
3.4.3
también es una sección que debe mirar: define a UB como el comportamiento "para el cual esta Norma Internacional no impone requisitos".C11 6.5.6/9
, teniendo en cuenta que la palabra "deberá" indica un requisito L "Cuando se restan dos punteros, ambos apuntarán a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz ".Los punteros son enteros, como todo lo demás en una computadora. Absolutamente puede compararlos con
<
y>
producir resultados sin hacer que un programa se bloquee. Dicho esto, el estándar no garantiza que esos resultados tengan ningún significado fuera de las comparaciones de matrices.En su ejemplo de variables asignadas de pila, el compilador es libre de asignar esas variables a registros o direcciones de memoria de pila, y en cualquier orden que elija. Las comparaciones como
<
y,>
por lo tanto, no serán consistentes entre los compiladores o arquitecturas. Sin embargo,==
y!=
no están tan restringidos, comparar la igualdad de punteros es una operación válida y útil.fuente
int x[10],y[10],*p;
, si el código evalúay[0]
, luego evalúap>(x+5)
y escribe*p
sin modificarp
mientras tanto, y finalmente evalúay[0]
nuevamente, ...(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
lugar deisalpha()
porque ¿qué implementación sensata tendría esos caracteres discontinuos? La conclusión es que, incluso si ninguna implementación que conoce tiene un problema, debe codificar el estándar tanto como sea posible si valora la portabilidad. Sin embargo, agradezco la etiqueta "estándares maven", gracias por eso. Puedo poner en mi CV :-)