Actualmente estoy leyendo un libro titulado "Recetas numéricas en C". En este libro, el autor detalla cómo ciertos algoritmos funcionan inherentemente mejor si tenemos índices que comienzan con 1 (no sigo completamente su argumento y ese no es el punto de esta publicación), pero C siempre indexa sus matrices comenzando con 0 Para evitar esto, sugiere simplemente disminuir el puntero después de la asignación, por ejemplo:
float *a = malloc(size);
a--;
Esto, dice, le dará efectivamente un puntero que tiene un índice que comienza con 1, que luego se liberará con:
free(a + 1);
Sin embargo, hasta donde yo sé, este es un comportamiento indefinido del estándar C. Aparentemente, este es un libro de gran reputación dentro de la comunidad de HPC, por lo que no quiero simplemente ignorar lo que está diciendo, pero simplemente decrementar un puntero fuera del rango asignado me parece muy incompleto. ¿Es este comportamiento "permitido" en C? Lo probé usando gcc y icc, y ambos resultados parecen indicar que no me preocupa nada, pero quiero ser absolutamente positivo.
Respuestas:
Tienes razón en ese código como
produce un comportamiento indefinido, según el estándar ANSI C, sección 3.3.6:
Para un código como este, la calidad del código C en el libro (cuando lo usé a fines de la década de 1990) no se consideraba muy alta.
El problema con el comportamiento indefinido es que no importa qué resultado produzca el compilador, ese resultado es, por definición, correcto (incluso si es altamente destructivo e impredecible).
Afortunadamente, muy pocos compiladores hacen un esfuerzo para causar un comportamiento inesperado en tales casos y la
malloc
implementación típica en las máquinas utilizadas para HPC tiene algunos datos de contabilidad justo antes de la dirección que devuelve, por lo que la disminución generalmente le daría un puntero a esos datos de contabilidad. No es una buena idea escribir allí, pero solo crear el puntero es inofensivo en esos sistemas.Solo tenga en cuenta que el código podría romperse cuando se cambia el entorno de tiempo de ejecución o cuando el código se transfiere a un entorno diferente.
fuente
Oficialmente, es un comportamiento indefinido tener un punto de puntero fuera de la matriz (excepto uno pasado el final), incluso si nunca se desreferencia .
En la práctica, si su procesador tiene un modelo de memoria plana (a diferencia de los extraños como x86-16 ), y si el compilador no le da un error de tiempo de ejecución u optimización incorrecta si crea un puntero no válido, entonces el código funcionará muy bien
fuente
Primero, es un comportamiento indefinido. Algunos compiladores optimizadores hoy en día se vuelven muy agresivos con el comportamiento indefinido. Por ejemplo, dado que a-- en este caso es un comportamiento indefinido, el compilador podría decidir guardar una instrucción y un ciclo de procesador y no disminuir a. Lo cual es oficialmente correcto y legal.
Ignorando eso, puede restar 1, 2 o 1980. Por ejemplo, si tengo datos financieros para los años 1980 a 2013, podría restar 1980. Ahora, si tomamos float * a = malloc (tamaño); seguramente hay una gran constante k tal que a - k es un puntero nulo. En ese caso, realmente esperamos que algo salga mal.
Ahora tome una estructura grande, digamos un megabyte de tamaño. Asigne un puntero p que apunte a dos estructuras. p - 1 puede ser un puntero nulo. p - 1 podría ajustarse (si una estructura es un megabyte, y el bloque malloc es de 900 KB desde el inicio del espacio de direcciones). Por lo tanto, podría ser sin ninguna malicia del compilador que p - 1> p. Las cosas pueden ponerse interesantes.
fuente
¿Permitido? Si. ¿Buena idea? No Usualmente.
C es una abreviatura de lenguaje ensamblador, y en lenguaje ensamblador no hay punteros, solo direcciones de memoria. Los punteros de C son direcciones de memoria que tienen un comportamiento secundario de aumento o disminución por el tamaño de lo que apuntan cuando se someten a aritmética. Esto hace que lo siguiente esté bien desde una perspectiva de sintaxis:
Las matrices no son realmente una cosa en C; son solo punteros a rangos contiguos de memoria que se comportan como matrices. El
[]
operador es una abreviatura para hacer aritmética de puntero y desreferenciar, por lo que ena[x]
realidad significa*(a + x)
.Hay razones válidas para hacer lo anterior, como algunos dispositivos de E / S que tienen un par de
double
s asignados a0xdeadbee7
y0xdeadbeef
. Muy pocos programas tendrían que hacer eso.Cuando crea la dirección de algo, como mediante el uso del
&
operador o la llamadamalloc()
, desea mantener intacto el puntero original para que sepa que lo que apunta en realidad es algo válido. Disminuir el puntero significa que un poco de código errante podría intentar desreferenciarlo, obteniendo resultados erróneos, golpeando algo o, dependiendo de su entorno, cometiendo una violación de segmentación. Esto es especialmente cierto conmalloc()
, porque usted ha puesto la carga sobre quien llamafree()
para recordar pasar el valor original y no alguna versión alterada que hará que se suelte todo.Si necesita matrices basadas en 1 en C, puede hacerlo de forma segura a expensas de asignar un elemento adicional que nunca se utilizará:
Tenga en cuenta que esto no hace nada para proteger contra exceder el límite superior, pero eso es lo suficientemente fácil de manejar.
Apéndice:
Algunos capítulos y versículos del borrador del C99 (lo siento, es todo lo que puedo vincular):
§6.5.2.1.1 dice que la segunda expresión ("otra") utilizada con el operador de subíndice es de tipo entero.
-1
es un número entero, y eso lo hacep[-1]
válido y, por lo tanto, también hace que el puntero sea&(p[-1])
válido. Esto no implica que acceder a la memoria en esa ubicación produzca un comportamiento definido, pero el puntero sigue siendo un puntero válido.§6.5.2.2 dice que el operador de subíndice de matriz evalúa el equivalente de agregar el número de elemento al puntero, por
p[-1]
lo tanto, es equivalente a*(p + (-1))
. Sigue siendo válido, pero puede no producir un comportamiento deseable.§6.5.6.8 dice (énfasis mío):
Esto significa que los resultados de la aritmética del puntero tienen que apuntar a un elemento en una matriz. No dice que la aritmética debe hacerse de una vez. Por lo tanto:
¿Recomiendo hacer las cosas de esta manera? No lo hago, y mi respuesta explica por qué.
fuente