2011-12-21 17 views
14

Por una razón u otra, deseo rodar manualmente una versión cero de malloc(). Para minimizar la complejidad algorítmica, quiero escribir:Memset() se llama con un puntero nulo si el tamaño es 0?

void * my_calloc(size_t size) 
{ 
    return memset(malloc(size), 0, size); 
} 

¿Es esta bien definido cuando size == 0? Está bien llamar al malloc() con un tamaño cero, pero eso le permite devolver un puntero nulo. ¿Será correcta la invocación posterior de memset, o es este comportamiento indefinido y necesito agregar un if (size) condicional?

¡Me gustaría evitar las verificaciones condicionales redundantes!

Supongamos por el momento que malloc() no falla. En realidad, habrá una versión enrollada a mano de malloc() allí, que terminará en caso de falla.

Algo como esto:

void * my_malloc(size_t size) 
{ 
    void * const p = malloc(size); 
    if (p || 0 == size) return p; 
    terminate(); 
} 

+0

AFAIK 'memset' no es necesario para comprobar NULL, por lo que si el' malloc' falla, va a cero bytes 'size' a partir de la dirección 0. – Praetorian

+0

@Praetorian: Lo siento, he añadido esto más adelante: Supongamos que' malloc() 'nunca falla. La pregunta es solo si 'size' puede ser' 0'. –

Respuesta

6

Editar Re:

que añadió esta tarde: Supongamos que malloc() nunca falla. La pregunta es solo si el tamaño puede ser 0

Veo. Por lo que sólo quiere que las cosas sean seguras si el puntero es NULL y el tamaño es 0.

En referencia a la documentación de POSIX

Sin , no se especifica que sea seguro llamar a memset con un puntero NULL (si lo llamaras con cero recuento o tamaño ... eso sería aún más 'inter esting ', pero tampoco especificado).

Nada de eso se menciona siquiera en las secciones "informativas".

Tenga en cuenta que el primer enlace menciona

la funcionalidad descrita en esta página de referencia está alineado con el estándar ISO C. Cualquier conflicto entre los requisitos descritos aquí y el estándar ISO C es involuntario. Este volumen de IEEE Std 1003.1-2001 se remite a la ISO C estándar

actualización puedo confirmar que el estándar ISO C99 (n1256.pdf) es igualmente breve como los docs POSIX y el C++ 11 especificación solo se refiere al estándar ansi C para memset y amigos.

+0

He navegado los estándares un poco antes de publicarlos :-) Debo haberlo perdido ... El objetivo de mi pregunta es la situación 'size == 0'. –

+0

Bueno, como es una función de biblioteca, quiero a) evitar condicionales innecesarios, yb) estar preparado para manejar cualquier entrada de usuario, incluso '0'. Si puedo lograr ambas cosas simplemente en virtud de las garantías de la biblioteca C, sería mucho mejor que agregar mis propios controles engorrosos. –

+0

Puedo confirmar que el estándar ISO C99 (n1256.pdf) es tan breve como los documentos POSIX ** y ** la especificación C++ 11 solo hace referencia al estándar ansi C para 'memset' y amigos. – sehe

9

Aquí está la declaración glibc:

extern void *memset (void *__s, int __c, size_t __n) __THROW __nonnull ((1)); 

El __nonnull muestra que espera que el puntero sea no nulo.

+0

Lo cual corrobora mi idea de que parece ** no especificado **, y en el caso de glibc, ** indefinido ** también (el prototipo simplemente hace doblemente seguro de que el compilador advierte que los resultados están _undefinidos_ al llamar con un puntero nulo) – sehe

4

Esto es lo que el estándar C99 dice acerca de esto:

7.1.4 "Uso de las funciones de biblioteca

Si un argumento a una función tiene un valor no válido (por ejemplo, un valor fuera del dominio de la función, o un puntero fuera del espacio de direcciones del programa, o un puntero nulo, o un puntero al almacenamiento no modificable cuando el parámetro correspondiente no está const-calificado) o un tipo (después de la promoción) no esperado por una función con número variable de argumentos, el comportamiento no está definido.

7.21.1 "convenciones función de cadena" (recuerde que memset() es en string.h)

Cuando un argumento declarado como size_t n especifica la longitud de la matriz para una función, n puede tener el valor cero en una llamar a esa función. A menos que se indique explícitamente lo contrario en la descripción de una función particular en esta subcláusula, los argumentos del puntero en dicha llamada aún tendrán valores válidos, como se describe en 7.1.4.

7.21.6.1 "La función memset"

El memset función copia el valor de c (convertido a un unsigned char) en cada uno de los n primeros caracteres del objeto apuntado por s.

En rigor, dado que la norma especifica que s debe apuntar a un objeto, pasar un puntero nulo sería UB. Agregue el cheque (el costo comparado con el malloc() será infinitamente pequeño). Por otro lado, si sabe que el malloc() no puede fallar (porque tiene uno personalizado que finaliza), entonces obviamente no necesita ejecutar el cheque antes de llamar al memset().

+0

Pero en mi interpretación, 'malloc (0)' tampoco puede fallar nunca, así que no estoy seguro de tener un puntero válido. ¿Estás diciendo que * es * definitivamente UB para llamar a 'memset' con un puntero no válido, incluso si el tamaño es 0? –

+0

@Kerrick: mi lectura del estándar es que es UB si pasa un puntero no válido, incluyendo 'NULL' - a' memset() ', incluso si el tamaño es' 0'. Me imagino que en la mayoría de las implementaciones funcionará como uno quisiera ('memset()' es un nop si 'size == 0'), pero eso es solo una feliz coincidencia de UB 'working'. Además, creo que hacer un argumento de rendimiento para no realizar la comprobación es débil: la puesta a cero del bloque de memoria dominará el rendimiento para el caso de uso de esta función que debería ser mucho más probable en órdenes de magnitud: cuando 'tamaño> 0'. –

+0

@Kerrick: Estoy un poco confundido acerca de su comentario con respecto a si el puntero es válido; si puede suponer que 'malloc (0)' no puede fallar, entonces puede llamar a 'memset()' con cero tamaño en el puntero devuelto por 'malloc (0)'. –

Cuestiones relacionadas