memcpy cero bytes en la variable const: ¿comportamiento indefinido?

8 minutos de lectura

Avatar de usuario de Jackson Allan
jackson allan

En C y C ++, ¿es un comportamiento indefinido para memcpy en un const variable cuando el número de bytes a copiar es cero?

int x = 0;
const int foo = 0;
memcpy( (void *)&foo, &x, 0 );

Esta pregunta no es puramente teórica. Tengo un escenario en el que memcpy se llama y si el puntero de destino apunta a const memoria, entonces se garantiza que el argumento de tamaño es cero. Así que me pregunto si necesito manejarlo como un caso especial.

  • ¿Por qué usar memcpy en C++? Para eso está std::copy. Todo el elenco (vacío*) ignorará cualquier constancia y seguridad de tipos (eso es muy importante en C++). También asegúrese de hacer su pregunta específicamente para “C” y “C ++”, son idiomas diferentes con reglas diferentes

    – Pepijn Kramer

    9 oct a las 14:38


  • Presumiblemente, si el destino es un puntero a const memoria, entonces es un puntero inválido y el comportamiento no está definido de acuerdo con preferencia cp.

    – Adrián Mole

    9 oct a las 14:41

  • ¿Por qué sería esto indefinido? Los lanzamientos de punteros torcidos suelen ser legales, es la deferencia (o escribir en el resultado de uno) lo que es ilegal.

    – Santo Gato Negro

    9 oct a las 14:45


  • @HolyBlackCat El estándar impone algunas limitaciones con respecto a memcpy que hacen que algunas cosas tengan un comportamiento sorprendentemente indefinido. Por ejemplo memcpy( NULL, NULL, 0 ) es un comportamiento técnicamente indefinido porque los punteros pasados ​​deben ser válidos, aunque en realidad no se esté produciendo ninguna copia. En cuanto a mi pregunta original, no pude encontrar nada en el estándar que cubra este escenario exacto, aunque puede haber algo allí.

    –Jackson Allan

    9 oct a las 15:07

  • @PepijnKramer “¿Por qué usar memcpy en C++?” – hay varias situaciones/rincones en C++ donde la única forma de escribir juegos de palabras sin UB es pasar por memcpypor lo que no es descabellado verlo en código C++.

    – Jesper Juhl

    9 oct a las 15:09


Avatar de usuario de Nate Eldredge
Nate Eldredge

c c17

La pregunta anterior ¿Se garantiza que sea seguro realizar memcpy (0,0,0)? señala 7.1.4p1:

Cada una de las siguientes declaraciones se aplica a menos que se indique explícitamente lo contrario en las descripciones detalladas que siguen: Si un argumento de una función tiene un valor no válido (como 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 a un almacenamiento no modificable cuando el parámetro correspondiente no está calificado como constante) o un tipo (después de la promoción) no esperado por una función con número variable de argumentos, el comportamiento es indefinido.

El prototipo para memcpy es

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

donde el primer parámetro no es const-cualificado, y &foo apunta a un almacenamiento no modificable. Entonces este código es UB a menos que la descripción de memcpy establece explícitamente lo contrario, lo cual no hace. Simplemente dice:

La función memcpy copia n caracteres del objeto al que apunta s2 en el objeto al que apunta s1.

Esto implica que memcpy con un conteo de 0 no copia ningún carácter (que también está confirmado por 7.24.1p2 “copia cero caracteres”, gracias Lundin), pero no lo exime del requisito de pasar argumentos válidos.

  • Dado que C++ no relaja los requisitos de la biblioteca C, esto es igualmente cierto para C++.

    – Transeúnte

    10 oct a las 5:51

  • Sospecho que esto será un error de Shroedinger: no hay absolutamente ninguna razón por la que llamarlo de la manera que lo describe cause algún problema; pero tan pronto como lo implemente, descubrirá que hay exactamente una biblioteca en el planeta que falla cuando hace esto, y es la que está usando.

    –Betty Crokker

    10 oct a las 12:23

  • @BettyCrokker O peor aún, la biblioteca estaría perfectamente bien si realmente la llamaras. Pero el compilador ve la llamada como un memcpy en caja especial e infiere que no puede suceder y elimina la ruta del código de llamada.

    – usuario1937198

    10 oct a las 13:40

  • “Esto implica que memcpy con un recuento de 0 no hace nada” C17 7.24.1/2 establece explícitamente que memcpy (una función de copia) copia cero bytes en caso de que el parámetro de tamaño sea cero.

    – Lundin

    10 oct a las 14:12

  • @Joshua: Un objetivo principal del estándar es permitir que los compiladores sean lo más útiles posible. Si en alguna plataforma, una implementación que se comportó de manera extraña cuando se le dio memcpy(0,0,0) sería genuinamente más útil para alguna tarea que una que la trataría como no operativa, creo que el Estándar estaría destinado a permitir tal tratamiento. Si no hay ningún caso en el que dicho tratamiento sea útil, entonces nadie debería tener ningún motivo para preocuparse por si la Norma permitiría tal tratamiento y, por lo tanto, no habría ninguna razón para que la Norma gastara tinta prohibiéndolo.

    – Super gato

    10 oct a las 18:14


avatar de usuario de supercat
Super gato

Está claro que en la gran mayoría de las plataformas, una implementación que procesaría memcpy(cualquier cosa, cualquier cosa, 0) como no operativo, independientemente de la validez de los punteros de origen y destino, sería en todos los sentidos, esencialmente en todos los no -escenario artificial, tan bueno o mejor que uno que hace cualquier otra cosa.

Sin embargo, la forma en que está escrito el estándar podría interpretarse como una especificación de que los compiladores pueden tratar como UB cualquier situación en la que la dirección de destino no esté asociada con almacenamiento grabable.

Si uno está utilizando una implementación que busca procesar casos de esquina de la manera útil prevista por los autores del Estándar, sin tener en cuenta si el Estándar exige sin ambigüedad tal comportamiento, todos memcpy y memmove las operaciones en las que el tamaño es cero se procesarán de forma fiable como sin operaciones. Si el tamaño a menudo será cero, puede haber ventajas de rendimiento al omitir un memcpy o memmove llame en el caso de tamaño cero, pero tal verificación nunca sería necesaria para la corrección.

Sin embargo, si uno desea garantizar una compatibilidad confiable con las configuraciones del compilador que asume agresivamente que el código nunca recibirá entradas que desencadenan casos extremos que no son 100 % inequívocamente exigidos por el Estándar, y está diseñado para generar código sin sentido si se reciben dichas entradas , entonces será necesario agregar una verificación de tamaño == 0 en cualquier caso en el que un tamaño cero pueda ir acompañado de algo que no sea un puntero al almacenamiento grabable, reconociendo que dicha verificación puede afectar negativamente el rendimiento en situaciones donde el tamaño es muy rara vez cero.

  • Los comentarios no son para una discusión extensa; esta conversación se ha movido al chat.

    – Samuel Liev

    11 de octubre a las 1:49

  • @supercat Cada vez que alguien hace una pregunta sobre el comportamiento indefinido en C en este sitio, publicas una versión de esta diatriba, y tengo que preguntarte, ¿por qué sigues publicándola? aquí, donde ninguna de las personas cuyas mentes necesitas cambiar lo leerá? Podría escribir un documento de posición para el comité C. Puede mencionarlo en las listas de correo de desarrollo de GCC o LLVM. Podría hacer una investigación real para respaldar sus afirmaciones y luego publicar en PLDI o ASPLOS. Tú tienes mejores opciones que ser ignorado aquí.

    – zwol

    12 oct a las 13:41

  • @zwol: mi respuesta se relaciona directamente con la pregunta formulada y menciona por qué garantizar un comportamiento definido podría aumentar potencialmente el costo de una implementación sencilla en algunas plataformas. No hay ninguna razón por la cual alguien que no esté apuntando a tales plataformas debería preocuparse por esas cosas, y hace 20 años creo que casi todo el mundo habría estado de acuerdo en que no había necesidad de preocuparse por ellas, pero una gran cantidad de código se procesa con compiladores que gratuitamente hacer es necesario preocuparse por tales cosas, no sea que las “optimizaciones” del compilador faciliten la ejecución de código remoto arbitrario.

    – Super gato

    12 oct a las 14:29


  • @zwol: ¿Puede alguien ser un buen programador de C hoy en día sin entender tanto (1) el Estándar no se escribió con la intención de hacer que los programadores salten aros gratuitos, y (2) algunos compiladores están diseñados para comportarse de manera peligrosamente absurda a menos que los programadores salten a través de aros gratuitos no previstos por la Norma? Si bien el n.° 2 puede parecer una afirmación escandalosa que no esperaría que la gente creyera sin pruebas, dado que es escandaloso pensar que los compiladores supuestamente de propósito general estarían diseñados de esa manera, la única forma en que uno podría decir que no lo estaban …

    – Super gato

    12 oct a las 14:54

  • No. Aquí a nadie le importa. Los carteles en este sitio solo se preocupan por lo que realmente tiene que hacer para que su código funcione hoy, lo que significa aceptar las interpretaciones del estándar de los compiladores de la generación actual como lo que es C. Una vez más, sería mucho mejor que escribiera un documento N para el comité, o un artículo académico sobre cuánto código “””heredado””” ha sido realmente descifrado por los compiladores actuales, o realmente cualquier cosa además escribiendo más o menos las mismas 500 palabras una y otra vez en un sitio donde a nadie le importa.

    – zwol

    12 oct a las 16:05

¿Ha sido útil esta solución?