Saltar al contenido

¿Cómo implementar memmove en C estándar sin una copia intermedia?

Solución:

Creo que tienes razón, no es posible implementar memmove eficientemente en estándar C.

La única forma verdaderamente portátil de probar si las regiones se superponen, creo, es algo como esto:

for (size_t l = 0; l < len; ++l) {
    if (src + l == dst) || (src + l == dst + len - 1) {
      // they overlap, so now we can use comparison,
      // and copy forwards or backwards as appropriate.
      ...
      return dst;
    }
}
// No overlap, doesn't matter which direction we copy
return memcpy(dst, src, len);

Tu tampoco puedes implementar memcpy o memmove todos ese de manera eficiente en código portátil, porque es probable que la implementación específica de la plataforma le dé una patada en el trasero, haga lo que haga. Pero un portátil memcpy al menos parece plausible.

C ++ introdujo una especialización de puntero de std::less, que se define para funcionar para dos punteros cualesquiera del mismo tipo. En teoría, podría ser más lento que <, pero obviamente en una arquitectura no segmentada no lo es.

C no tiene tal cosa, así que en cierto sentido, el estándar C ++ está de acuerdo con usted en que C no tiene suficiente comportamiento definido. Pero entonces, C ++ lo necesita para std::map etcétera. Es mucho más probable que desee implementar std::map (o algo así) sin conocimiento de la implementación de lo que le gustaría implementar memmove (o algo parecido) sin conocimiento de la implementación.

Para que dos áreas de memoria sean válidas y se superpongan, creo que debería estar en una de las situaciones definidas en 6.5.8.5. Es decir, dos áreas de una matriz, unión, estructura, etc.

La razón por la que otras situaciones no están definidas es porque es posible que dos objetos diferentes ni siquiera estén en el mismo tipo de memoria, con el mismo tipo de puntero. En las arquitecturas de PC, las direcciones suelen ser direcciones de 32 bits en la memoria virtual, pero C admite todo tipo de arquitecturas extrañas, donde la memoria no es nada de eso.

La razón por la que C deja las cosas sin definir es para dar margen de maniobra a los redactores del compilador cuando no es necesario definir la situación. La forma de leer 6.5.8.5 es un párrafo que describe cuidadosamente las arquitecturas que C quiere admitir donde la comparación de punteros no tiene sentido a menos que esté dentro del mismo objeto.

Además, la razón por la que el compilador proporciona memmove y memcpy es que a veces se escriben en un ensamblado ajustado para la CPU de destino, utilizando una instrucción especializada. No están destinados a poder implementarse en C con la misma eficiencia.

Para empezar, el estándar C es conocido por tener problemas en los detalles como este. Parte del problema se debe a que C se usa en múltiples plataformas y el estándar intenta ser lo suficientemente abstracto como para cubrir todas las plataformas actuales y futuras (que podrían usar un diseño de memoria complicado que va más allá de todo lo que hayamos visto). Existe una gran cantidad de comportamiento indefinido o específico de la implementación para que los redactores del compilador “hagan lo correcto” para la plataforma de destino. Incluir detalles para cada plataforma no sería práctico (y estaría constantemente desactualizado); en cambio, el estándar C deja que el escritor del compilador documente lo que sucede en estos casos. El comportamiento “no especificado” solo significa que el estándar C no especifica lo que sucede, no necesariamente que el resultado no se pueda predecir. El resultado suele ser predecible si lee la documentación de su plataforma de destino y su compilador.

Dado que determinar si dos punteros apuntan al mismo bloque, segmento de memoria o espacio de direcciones depende de cómo se distribuya la memoria para esa plataforma, la especificación no define una forma de hacer esa determinación. Supone que el compilador sabe cómo hacer esta determinación. La parte de la especificación que citó dice que el resultado de la comparación de punteros depende de la “ubicación relativa de los punteros en el espacio de direcciones”. Observe que aquí el “espacio de direcciones” es singular. Esta sección solo se refiere a punteros que están en el mismo espacio de direcciones; es decir, punteros que son directamente comparables. Si los punteros están en espacios de direcciones diferentes, el resultado no está definido por el estándar C y, en cambio, está definido por los requisitos de la plataforma de destino.

En el caso de memmove, el implementador generalmente determina primero si las direcciones son directamente comparables. De lo contrario, el resto de la función es específica de la plataforma. La mayoría de las veces, estar en diferentes espacios de memoria es suficiente para garantizar que las regiones no se superpongan y la función se convierta en una memcpy. Si las direcciones son directamente comparables, entonces es solo un proceso de copia de bytes simple que comienza desde el primer byte y avanza o desde el último byte y retrocede (cualquiera que copiará los datos de manera segura sin golpear nada).

Con todo, el estándar C deja mucho sin especificar intencionalmente donde no puede escribir una regla simple que funcione en cualquier plataforma de destino. Sin embargo, los escritores estándar podrían haber hecho un mejor trabajo al explicar por qué algunas cosas no están definidas y se utilizan términos más descriptivos como “dependiente de la arquitectura”.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *