¿Puedo llamar a memcpy () y memmove () con "número de bytes" establecido en cero?

102

¿Necesito tratar casos en los que realmente no tengo nada para mover / copiar memmove()/ memcpy()como casos extremos?

int numberOfBytes = ...
if( numberOfBytes != 0 ) {
    memmove( dest, source, numberOfBytes );
}

o debería simplemente llamar a la función sin verificar

int numberOfBytes = ...
memmove( dest, source, numberOfBytes );

¿Es necesaria la verificación en el fragmento anterior?

diente filoso
fuente
6
La pregunta me recuerda un poco la comprobación de punteros nulos en funciones como free. No es necesario, pero pondría un comentario allí para mostrar que pensaste en ello.
Sapo
12
@Toad: ¿Para qué sirve eso, además de desordenar el código? Al leer el código de alguien, no necesito saber que el programador original "pensó en hacer esta operación que en realidad no es necesaria, pero como es innecesaria, no la hice". Si veo que se libera un puntero, que se permite que sea nulo, por lo que no necesito conocer los pensamientos del programador original sobre el tema "¿debo verificar si es nulo". Y lo mismo ocurre con la copia de 0 bytes conmemcpy
jalf
8
@jalf: el hecho de que sea una pregunta sobre stackoverflow, hace que la gente dude. Por lo que agregar un comentario que no puede ayudar, pero podría ayudar a alguien con menos conocimiento
sapo
2
@Toad Sí, los comentarios que expliquen explícitamente por qué un cheque que parece necesario realmente no lo es, pueden ser valiosos en principio. La otra cara de la moneda es que este ejemplo en particular es un caso común que involucra una función de biblioteca estándar para la que cada programador solo necesita aprender la respuesta una vez; entonces pueden reconocer en cualquier programa que lean que estos controles no son necesarios. Por esa razón, omitiría los comentarios. Una base de código con múltiples llamadas como esta necesitará copiar y pegar los comentarios en cada una, o usarlos arbitrariamente solo en algunas llamadas, las cuales son feas.
Mark Amery

Respuestas:

145

Del estándar C99 (7.21.1 / 2):

Donde un argumento declarado como size_t nespecifica la longitud de la matriz para una función, npuede tener el valor cero en una llamada 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 de puntero en dicha llamada todavía tendrán valores válidos, como se describe en 7.1.4. En tal llamada, una función que localiza un carácter no encuentra ocurrencia, una función que compara dos secuencias de caracteres devuelve cero y una función que copia caracteres copia cero caracteres.

Por tanto, la respuesta es no; la verificación no es necesaria (o sí, puede pasar cero).

Mike Seymour
fuente
1
¿Se consideraría "válido" un puntero para los propósitos de dicha función si apuntara a la ubicación que sigue al último elemento de una matriz? Tal puntero no podría ser deferenciado legítimamente, pero uno podría hacer con seguridad otras cosas similares a puntero, como restarle uno.
supercat
1
@supercat: sí, un puntero que apunta uno más allá del final de una matriz es válido para la aritmética de punteros con otros punteros dentro (o uno más allá del final de) esa matriz, pero no es desreferenciable.
Mike Seymour
@MikeSeymour: ¿No debería la cita implicar una respuesta opuesta: la verificación es necesaria y no puede pasar cero con punteros nulos?
neverhoodboy
7
@neverhoodboy: No, la cita dice claramente " npuede tener el valor cero". Tiene razón en que no puede pasar punteros nulos, pero eso no es sobre lo que pregunta la pregunta.
Mike Seymour
1
@MikeSeymour: Mi culpa. Lo siento mucho. La pregunta es sobre el tamaño, no sobre el puntero.
neverhoodboy
5

Como dice @You, el estándar especifica que memcpy y memmove deben manejar este caso sin problemas; ya que generalmente se implementan de alguna manera como

void *memcpy(void *_dst, const void *_src, size_t len)
{
    unsigned char *dst = _dst;
    const unsigned char *src = _src;
    while(len-- > 0)
        *dst++ = *src++;
    return _dst;
}

ni siquiera debería tener ninguna penalidad de rendimiento que no sea la llamada a la función; si el compilador admite intrínsecos / inlining para tales funciones, la verificación adicional puede incluso hacer que el código sea un poco más lento, ya que la verificación ya está hecha en ese momento.

Matteo Italia
fuente
1
Creo que esta función probablemente se realiza en ensamblador, donde puede optimizar las transferencias de memoria mucho mejor que en c
Toad
"de alguna manera como" :) En realidad, casi todas las implementaciones que he visto están en ensamblador e intentan copiar la mayoría de los bits usando el tamaño de la palabra nativa (por ejemplo, uint32_t en x86), pero eso no cambia la esencia de la respuesta : es un tiempo de bucle que no necesita de grandes cálculos antes de empezar, por lo que el cheque ya está hecho.
Matteo Italia
9
-1, la implementación típica es irrelevante para que C sea válido para llamar a estas funciones (que ni siquiera pueden implementarse como funciones de C) con un argumento cero.
R .. GitHub DEJA AYUDAR A ICE
4
El hecho de que sea una C válida ya ha sido cubierto por las otras respuestas, como dije al principio de mi respuesta: "Como dijo @You, el estándar especifica que memcpy y memmove deben manejar este caso sin problemas". Acabo de agregar mi opinión sobre el hecho de que ni siquiera debes tener miedo de llamar a memcpy con len = 0 por razones de rendimiento, ya que en ese caso es una llamada con un costo casi nulo.
Matteo Italia