legalidad técnica de asignaciones de punteros incompatibles

6 minutos de lectura

avatar de usuario
gha.st

El estándar C11 ISO/IEC 9899:2011 (E) establece las siguientes restricciones para asignaciones simples en §6.5.16.1/1:

Se cumplirá uno de los siguientes:

  • el operando izquierdo tiene tipo aritmético atómico, calificado o no calificado, y el derecho tiene tipo aritmético;
  • el operando izquierdo tiene una versión atómica, calificada o no calificada de una estructura o tipo de unión compatible con el tipo de la derecha;
  • el operando izquierdo tiene un tipo de puntero atómico, calificado o no calificado y (considerando el tipo que tendría el operando izquierdo después de la conversión de lvalue) ambos operandos son punteros a versiones calificadas o no calificadas de tipos compatibles, y el tipo al que apunta la izquierda tiene todos los calificativos del tipo señalado por la derecha;
  • el operando izquierdo tiene un tipo de puntero atómico, calificado o no calificado y (considerando el tipo que tendría el operando izquierdo después de la conversión de lvalue) un operando es un puntero a un tipo de objeto y el otro es un puntero a una versión calificada o no calificada de nulo, y el tipo señalado por la izquierda tiene todos los calificadores del tipo señalado por la derecha;
  • el operando izquierdo es un puntero atómico, calificado o no calificado, y el derecho es una constante de puntero nulo; o
  • el operando izquierdo tiene tipo atómico, calificado o no calificado _Booly la derecha es un puntero.

Estoy interesado en el caso en el que ambos lados son punteros a incompatible tipos diferentes de void. Si entiendo correctamente, esto debería al menos invocar UB, ya que viola esta restricción. Un ejemplo de tipos incompatibles debería ser (según §6.2.7 y §6.7.2) int y double.

Por lo tanto, el siguiente programa debería estar en violación:

int main(void) {
  int a = 17;
  double* p;
  p = &a;
  (void)p;
}

Tanto gcc como clang advierten sobre “-Wincompatible-pointer-types”, pero no cancelan la compilación (compilación con -std=c11 -Wall -Wextra -pedantic).

De manera similar, el siguiente programa solo conduce a una advertencia de “-Wint-conversion”, mientras se compila correctamente.

int main(void) {
  int a;
  double* p;
  p = a;
  (void)p;
}

Viniendo de C ++, esperaba que cualquiera de esos casos de prueba requiriera un molde para compilar. ¿Hay alguna razón por la cual cualquiera de los programas sería legal para los estándares? O, ¿existen al menos razones históricas significativas para admitir este estilo de código incluso cuando se deshabilitan las entretenidas extensiones GNU C al usar explícitamente -std=c11 en vez de -std=gnu11?

El indicador del compilador (tanto gcc como clang) para solicitar comprobaciones de conformidad estricta con los estándares y negarse a compilar código no conforme es -pedantic-errors:

$ gcc -std=c11 -pedantic-errors x.c
x.c: In function ‘main’:
x.c:3:15: error: initialization from incompatible pointer type [-Wincompatible-pointer-types]
   double* p = &a;
               ^

Sonido metálico:

$ clang -std=c11 -pedantic-errors x.c
x.c:3:11: error: incompatible pointer types initializing 'double *' with an
      expression of type 'int *' [-Werror,-Wincompatible-pointer-types]
  double* p = &a;
          ^   ~~
1 error generated.

Una proporción significativa (por decir lo menos) del código C típico en la naturaleza no es conforme, por lo que -pedantic-errors haría que la mayoría de los programas y bibliotecas C fallaran al compilar.

  • No, parece más un problema de la cadena de herramientas. En mi máquina, gcc y clang dan el diagnóstico. Así que no, -pendantic no es el camino a seguir. La forma es actualizar la plataforma.

    – Jens Gusted

    25 de abril de 2016 a las 8:26

  • Aparentemente, GCC siempre da una advertencia para esto, pero para evitar que se compile el código C incorrecto, necesita -pedantic-errors.

    – Lundin

    25 de abril de 2016 a las 11:41

  • Por supuesto, tiene razón en que yo (durante el curso de escribir esto) acorté la asignación a una inicialización. Sin embargo, el comportamiento de ambos compiladores es el mismo para la asignación también.

    – gha.st

    24/04/2016 a las 23:02

  • Tenga en cuenta también §6.7.9/11, que esencialmente dice que se trata de la misma manera que una asignación en este caso: “El inicializador para un escalar será una sola expresión, opcionalmente encerrada entre llaves. El valor inicial del objeto es el de la expresión (después de la conversión); se aplican las mismas restricciones de tipo y conversiones que para la asignación simple, tomando el tipo del escalar como la versión no calificada de su tipo declarado”.

    – gha.st

    24 de abril de 2016 a las 23:08

  • Yo mismo me sorprendí al encontrar que el estado en el estándar es diferente entre los dos. Para conocer la realidad del problema, consulte mi edición.

    – Jens Gusted

    25 de abril de 2016 a las 8:24

  • Para el registro, GCC da dos advertencias diferentes para “puntero de entero sin conversión”, una durante la inicialización y otra durante la asignación.

    – Lundin

    25 de abril de 2016 a las 11:48

avatar de usuario
DigitalRoss

No si


Es realmente muy simple.

Diagrama de Venn de código conforme

No, no las normas legales. Sí, significativo razones históricas.

C originalmente ni siquiera tenía moldes. Como lenguaje de programación de sistemas, usarlo como un ensamblador glorificado ultrapoderoso no solo era razonable, sino que era la mejor práctica y realmente “solo práctica”, en el pasado.

Una pieza clave de información debería arrojar luz sobre las cosas: realmente no es trabajo del compilador implementar o hacer cumplir la especificación. La especificación es, en realidad, literalmente, solo una sugerencia. El trabajo real del compilador es compilar todo el código C que se haya escrito alguna vez, incluidos los anteriores a C11, anteriores a C99, anteriores a C89 e incluso anteriores a K&R. Es por eso que hay tantas restricciones opcionales. Los proyectos con estilos de código modernos activan modos estrictamente conformes.

Existe la forma en que C se define en los estándares y existe la forma en que C se usa en la práctica. Como puede ver, el compilador simplemente no puede negarse a construir el código.

Durante décadas, los desarrolladores han estado cambiando a un código portátil y estrictamente conforme, pero cuando apareció C por primera vez, se usó un poco como un ensamblador increíblemente poderoso, y fue una especie de temporada abierta en la aritmética de direcciones y juegos de palabras. Los programas en esos días se escribieron principalmente para una arquitectura a la vez.

  • Por supuesto, los autores de gcc tienen un punto de vista bastante diferente: los compiladores no tienen el deber de generar ningún tipo de comportamiento útil cuando se ajustan a un código que no se ajusta estrictamente, incluso cuando todos los demás compiladores para la misma plataforma habrían producido un comportamiento útil. comportamiento que no se puede lograr en un código estrictamente conforme.

    – Super gato

    12 mayo 2016 a las 22:24

  • Por supuesto, los autores de gcc tienen un punto de vista bastante diferente: los compiladores no tienen el deber de generar ningún tipo de comportamiento útil cuando se ajustan a un código que no se ajusta estrictamente, incluso cuando todos los demás compiladores para la misma plataforma habrían producido un comportamiento útil. comportamiento que no se puede lograr en un código estrictamente conforme.

    – Super gato

    12 mayo 2016 a las 22:24

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad