c language-lawyer

¿El uso de memcmp en la matriz de int es estrictamente conforme?



language-lawyer (2)

¿Es el siguiente programa un programa estrictamente conforme en C? Estoy interesado en c90 y c99 pero las respuestas de c11 también son aceptables.

#include <stdio.h> #include <string.h> struct S { int array[2]; }; int main () { struct S a = { { 1, 2 } }; struct S b; b = a; if (memcmp(b.array, a.array, sizeof(b.array)) == 0) { puts("ok"); } return 0; }

En comentarios a mi respuesta en una pregunta diferente , Eric Postpischil insiste en que la salida del programa cambiará dependiendo de la plataforma, principalmente debido a la posibilidad de bits de relleno no inicializados. Pensé que la asignación de estructura sobrescribiría todos los bits en b para que fueran los mismos que en a . Pero, C99 no parece ofrecer tal garantía. De la Sección 6.5.16.1 p2:

En asignación simple ( = ), el valor del operando derecho se convierte al tipo de expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando izquierdo.

¿Qué se entiende por "convertido" y "reemplaza" en el contexto de los tipos compuestos?

Finalmente, considere el mismo programa, excepto que las definiciones de a y b se hacen globales. ¿Sería ese programa un programa estrictamente conforme?

Edición: Solo quería resumir parte del material de discusión aquí, y no agregar mi propia respuesta, ya que realmente no tengo una de mi propia creación.

  • El programa no es estrictamente conforme. Dado que la asignación es por valor y no por representación, b.array puede o no contener bits establecidos de manera diferente a a.array .
  • a no necesita ser convertido ya que es del mismo tipo que b , pero el reemplazo es por valor y hecho miembro por miembro.
  • Incluso si las definiciones en a y b se hacen globales, post asignación, b.array puede o no contener bits establecidos de manera diferente de a.array . (Hubo poca discusión sobre los bytes de relleno en b , pero la pregunta publicada no fue sobre la comparación de la estructura. C99 carece de una mención de cómo se inicializa el relleno en el almacenamiento estático, pero c11 declara explícitamente que es cero inicializado).
  • En una nota lateral, hay acuerdo en que memcmp está bien definido si b se inicializó con memcpy desde a .

Mi agradecimiento a todos los involucrados en la discusión.


En C99 §6.2.6

§6.2.6.1 General

1 Las representaciones de todos los tipos no están especificadas, excepto lo indicado en esta subcláusula.

[...]

4 [..] Dos valores (distintos de NaN) con la misma representación de objeto se comparan igual, pero los valores que comparan igual pueden tener diferentes representaciones de objeto.

6 Cuando un valor se almacena en un objeto de estructura o tipo de unión, incluido en un objeto miembro, los bytes de la representación del objeto que corresponden a cualquier octeto de relleno toman valores no especificados. 42)

42) Por lo tanto, por ejemplo, la asignación de estructura no necesita copiar ningún bit de relleno.

43) Es posible que los objetos xey con el mismo tipo efectivo T tengan el mismo valor cuando se acceda a ellos como objetos de tipo T, pero tengan valores diferentes en otros contextos. En particular, si se define == para el tipo T, entonces x == y no implica que memcmp (& x, & y, sizeof (T)) == 0. Además, x == y no necesariamente implica que x e y tienen el mismo valor; Otras operaciones en valores de tipo T pueden distinguir entre ellas.

§6.2.6.2 Tipos de enteros

[...]

2 Para los tipos de enteros con signo, los bits de la representación del objeto se dividirán en tres grupos: bits de valor, bits de relleno y el bit de signo. No es necesario que haya bits de relleno; [...]

[...]

5 Los valores de cualquier bit de relleno no están especificados. [...]

En J.1 Comportamiento no especificado

  • El valor de los bytes de relleno al almacenar valores en estructuras o uniones (6.2.6.1).

[...]

  • Los valores de cualquier bit de relleno en representaciones de enteros (6.2.6.2).

Por lo tanto, puede haber bits en la representación de a y b que difieran sin afectar el valor. Esta es la misma conclusión que la otra respuesta, pero pensé que estas citas del estándar serían un buen contexto adicional.

Si haces un memcpy entonces el memcmp siempre devolverá 0 y el programa se memcmp estrictamente. El memcpy duplica la representación del objeto de a en b .


Mi opinión es que es estrictamente conforme. Según 4.5 que mencionó Eric Postpischil:

Un programa estrictamente conforme utilizará únicamente las características del idioma y la biblioteca especificados en esta Norma Internacional. No producirá resultados dependientes de ningún comportamiento no especificado, indefinido o definido por la implementación, y no deberá exceder ningún límite mínimo de implementación.

El comportamiento en cuestión es el comportamiento de memcmp , y está bien definido, sin ningún memcmp no especificado, indefinido o definido por la implementación. Funciona en los bits sin procesar de la representación, sin saber nada acerca de los valores, los bits de relleno o las representaciones de trampas. Por lo tanto, el resultado (pero no la funcionalidad ) de memcmp en este caso específico depende de la implementación de los valores almacenados dentro de estos bytes.

Nota 43) en 6.2.6.2:

Es posible que los objetos xey con el mismo tipo efectivo T tengan el mismo valor cuando se acceda a ellos como objetos de tipo T, pero tengan valores diferentes en otros contextos. En particular, si se define == para el tipo T, entonces x == y no implica que memcmp (& x, & y, sizeof (T)) == 0. Además, x == y no necesariamente implica que x e y tienen el mismo valor; Otras operaciones en valores de tipo T pueden distinguir entre ellas.

EDITAR:

Pensándolo un poco más, ya no estoy tan seguro de lo estrictamente conforme con esto:

No producirá salida dependiente de ningún no especificado [...]

Claramente, el resultado de memcmp depende del comportamiento no especificado de la representación, cumpliendo así esta cláusula, aunque el comportamiento de memcmp sí está bien definido. La cláusula no dice nada sobre la profundidad de la funcionalidad hasta que se produce la salida.

Por lo tanto, no es estrictamente conforme.

EDIT 2:

No estoy tan seguro de que se ajustará estrictamente cuando se utilice memcpy para copiar la estructura. De acuerdo con el Anexo J, el comportamiento no especificado ocurre cuando se inicializa a:

struct S a = { { 1, 2 } };

Incluso si asumimos que los bits de relleno no cambiarán y memcpy siempre devuelve 0, seguirá utilizando los bits de relleno para obtener su resultado. Y se basa en el supuesto de que no cambiarán, pero no hay ninguna garantía en la norma al respecto.

Debemos diferenciar los rellenos de bytes en las estructuras, que se utilizan para la alineación, y los bits de relleno en tipos nativos específicos como int . Si bien podemos asumir con seguridad que los bytes de relleno no cambiarán, pero solo porque no hay una razón real para ello, lo mismo no se aplica a los bits de relleno. El estándar menciona una bandera de paridad como un ejemplo de un bit de relleno. Esta puede ser una función de software de la implementación, pero también puede ser una función de hardware. Por lo tanto, puede haber otros indicadores de hardware utilizados para los bits de relleno, incluido uno que cambie en los accesos de lectura por cualquier motivo.

Tendremos dificultades para encontrar una máquina tan exótica y una implementación, pero no veo nada que lo prohíba. Corrígeme si me equivoco.