tipos que programacion programa partes interprete ejemplos compilar compiladores compilador compilacion capas c gcc clang language-lawyer compiler-optimization

que - ¿Se permite a los compiladores optimizar el realloc?



que es un interprete en programacion (4)

Me encontré con una situación en la que sería útil tener llamadas innecesarias a realloc estaban optimizando. Sin embargo, parece que ni clang ni gcc hacen tal cosa ( godbolt ). - Aunque veo que se hacen optimizaciones con múltiples llamadas a malloc .

El ejemplo:

void *myfunc() { void *data; data = malloc(100); data = realloc(data, 200); return data; }

Esperaba que estuviera optimizado para algo como lo siguiente:

void *myfunc() { return malloc(200); }

¿Por qué ni clang ni gcc lo optimizan? - ¿No se les permite hacerlo?


¿No se les permite hacerlo?

Tal vez, pero la optimización no realizada en este caso puede deberse a diferencias funcionales en las esquinas.

Si quedan 150 bytes de memoria asignable,
data = malloc(100); data = realloc(data, 200); devuelve NULL con 100 bytes consumidos (y filtrados) y 50 permanecen.

data = malloc(200); devuelve NULL con 0 bytes consumidos (ninguno filtrado) y 150 permanecen.

Diferente funcionalidad en este caso estrecho puede impedir la optimización.

¿Se permite a los compiladores optimizar el realloc?

Quizás, yo esperaría que esté permitido. Sin embargo, puede que no valga la pena el efecto de mejorar el compilador para determinar cuándo puede hacerlo.

Exitoso malloc(n); ... realloc(p, 2*n) malloc(n); ... realloc(p, 2*n) difiere de malloc(2*n); cuando ... puede haber puesto algo de memoria.

Podría estar más allá del diseño de ese compilador para asegurar ... , incluso si el código vacío, no configuró ninguna memoria.


El compilador puede optimizar múltiples llamadas a funciones que se consideran funciones puras , es decir, funciones que no tienen ningún efecto secundario.

Entonces la pregunta es si realloc() es una función pura o no.

El borrador del Comité Estándar C11 N1570 establece esto sobre la función de realloc :

7.22.3.5 La función realloc
...
2. La función realloc desasigna el objeto antiguo al que apunta ptr y devuelve un puntero a un nuevo objeto que tiene el tamaño especificado por tamaño. El contenido del nuevo objeto debe ser el mismo que el anterior antes de la desasignación, hasta el menor de los nuevos y antiguos tamaños. Cualquier octeto en el nuevo objeto más allá del tamaño del antiguo objeto tiene valores indeterminados.

Devoluciones
4. La función realloc devuelve un puntero al nuevo objeto (que puede tener el mismo valor que un puntero al objeto antiguo), o un puntero nulo si no se pudo asignar el nuevo objeto.

Observe que el compilador no puede predecir en el momento de la compilación el valor del puntero que se devolverá en cada llamada.

Esto significa que realloc() no puede considerarse una función pura y que el compilador no puede optimizar múltiples llamadas.


Pero no está verificando el valor de retorno del primer malloc () que está utilizando en el segundo realloc (). Igual podría ser NULL.

¿Cómo podría el compilador optimizar las dos llamadas en una sola sin hacer suposiciones injustificadas sobre el valor de retorno de la primera?

Entonces hay otro escenario posible. FreeBSD solía tener un realloc() que básicamente era malloc + memcpy + libre del puntero antiguo.

Supongamos que solo quedan 230 bytes de memoria libre. En esa implementación, ptr = malloc(100) seguido de realloc(ptr, 200) fallará, pero un solo malloc(200) tendrá éxito.


Un compilador que agrupe sus propias versiones autocontenidas de malloc / calloc / free / realloc podría realizar legítimamente la optimización indicada si los autores pensaban que valía la pena el esfuerzo. Un compilador que se encadene a funciones suministradas externamente todavía podría realizar tales optimizaciones si documentara que no consideraba la secuencia precisa de llamadas a tales funciones como un efecto secundario observable, pero tal tratamiento podría ser un poco más tenue.

Si no se asigna o desasigna ningún almacenamiento entre malloc () y realloc (), el tamaño de realloc () se conoce cuando se realiza malloc (), y el tamaño de realloc () es mayor que el tamaño de malloc (), entonces puede tener sentido consolidar las operaciones malloc () y realloc () en una sola asignación más grande. Sin embargo, si el estado de la memoria podría cambiar en el ínterin, tal optimización podría provocar el fallo de las operaciones que deberían haber tenido éxito. Por ejemplo, dada la secuencia:

void *p1 = malloc(2000000000); void *p2 = malloc(2); free(p1); p2 = realloc(p2, 2000000000);

es posible que un sistema no tenga 2000000000 bytes disponibles para p2 hasta que se libere p1. Si fuera a cambiar el código a:

void *p1 = malloc(2000000000); void *p2 = malloc(2000000000); free(p1);

que daría lugar a la asignación de p2 falla. Debido a que la Norma nunca garantiza que las solicitudes de asignación tengan éxito, tal comportamiento no sería no conforme. Por otro lado, lo siguiente también sería una implementación "conforme":

void *malloc(size_t size) { return 0; } void *calloc(size_t size, size_t count) { return 0; } void free(void *p) { } void *realloc(void *p, size_t size) { return 0; }

Tal implementación podría considerarse como más "eficiente" que la mayoría de las otras, pero uno tendría que ser bastante obtuso para considerarla como muy útil, excepto, quizás, en situaciones raras donde las funciones anteriores se llaman en las rutas de código que son nunca ejecutado

Creo que el Estándar permitiría claramente la optimización, al menos en casos que son tan simples como los de la pregunta original. Incluso en los casos en los que podría ocasionar fallas en las operaciones que de otro modo podrían haber tenido éxito, el Estándar todavía lo permitiría. Probablemente, la razón por la que muchos compiladores no realizan la optimización es que los autores no pensaron que los beneficios serían suficientes para justificar el esfuerzo requerido para identificar casos en los que sería seguro y útil.