Un ejemplo de comportamiento no especificado en el lenguaje C es el orden de evaluación de argumentos a una función. Se puede ser de izquierda a derecha o de derecha a izquierda, solo que usted no sabe. Esto afectaría la manera en que foo(c++, c) o foo(++c, c) es evaluada.

Lo que otros no especificados de comportamiento está ahí, que puede sorprender a los desprevenidos programador?

  • foo(c++, c) y foo(++c, c) son tanto un comportamiento indefinido, que los triunfos no especificado.
InformationsquelleAutor Benoit | 2008-09-19

13 Comentarios

  1. 72

    Un lenguaje abogado de que se trate. Hmkay.

    Personal de mi top3:

    1. violar la estricta aliasing regla
    2. violar la estricta aliasing regla
    3. violar la estricta aliasing regla

      🙂

    Editar he Aquí un pequeño ejemplo que hace mal dos veces:

    (asumir 32 bits enteros y little endian)

    float funky_float_abs (float a)
    {
      unsigned int temp = *(unsigned int *)&a;
      temp &= 0x7fffffff;
      return *(float *)&temp;
    }

    Que el código que se intenta obtener el valor absoluto de un flotador por poco-cambiando con el bit de signo directamente en la representación de un flotador.

    Sin embargo, el resultado de la creación de un puntero a un objeto por la conversión de un tipo a otro no es válido C. El compilador puede asumir que los punteros a los diferentes tipos de no apuntar a la misma cantidad de memoria. Esto es cierto para todo tipo de punteros excepto void* y char* (signo-ness no importa).

    Que en el caso anterior tengo que hacer dos veces. Una vez que obtener una int-alias para el float a, y de una vez para convertir el valor a flotar.

    Hay tres formas válidas de hacer lo mismo.

    Usar un char o un puntero nulo durante el reparto. Estos siempre alias para nada, por lo que son seguros.

    float funky_float_abs (float a)
    {
      float temp_float = a;
      //valid, because it's a char pointer. These are special.
      unsigned char * temp = (unsigned char *)&temp_float;
      temp[3] &= 0x7f;
      return temp_float;
    }

    Uso memcopy. Memcpy toma punteros void, por lo que la fuerza de aliasing así.

    float funky_float_abs (float a)
    {
      int i;
      float result;
      memcpy (&i, &a, sizeof (int));
      i &= 0x7fffffff;
      memcpy (&result, &i, sizeof (int));
      return result;
    }

    La tercera válida: el uso de los sindicatos. Esto es explícitamente no definido ya que C99:

    float funky_float_abs (float a)
    {
      union 
      {
         unsigned int i;
         float f;
      } cast_helper;
    
      cast_helper.f = a;
      cast_helper.i &= 0x7fffffff;
      return cast_helper.f;
    }
    • Esto suena interesante…se puede ampliar?
    • aehm. He mencionado que supongo 32 bits enteros y little endian. Por cierto, – la unión de uso es todavía un comportamiento indefinido no a causa de la IEEE de bits de la representación, sino simplemente porque (en teoría) no se permite escribir en el campo de f y leer a partir de trabajo de campo.
    • onebyone, es un comportamiento indefinido, incluso si la aplicación utiliza la ieee. el punto es que se lee de un miembro diferente que fue el último escrito.
    • csci.csusb.edu/dick/c++std/cd2/basic.html#basic.lval viñeta 15 parece dar a entender que tipo descriptivo a través de una unión segura. La redacción en el estándar de c es idéntica.
    • el estándar C99 permite de tipo descriptivo a través de los sindicatos; véase la nota de pie de página 82, que fue añadido con TC3: «Si el miembro de que se utiliza para acceder a los contenidos de una unión de objeto no es el mismo como el último miembro utiliza para almacenar un valor en el objeto, la parte pertinente de la representación de objetos de valor se reinterpreta como un objeto de representación en el nuevo tipo descrito en 6.2.6 (un proceso que a veces se llama «tipo descriptivo»). Esto podría ser una trampa de la representación».
    • No entiendo qué tiene de especial char* y*vacío.(aparte de ser byte alineados). No podemos reescribir la primera funky función de la siguiente? float funky_float_abs (float a) { unsigned int * temp = (unsigned int) a; temp = (unsigned int*)((int)temp & 0x7fffffff); return (float)temp; }
    • Creo que hay una errata en esta línea : unsigned char * temp = (unsigned char *) a;. Creo que quería escribir unsigned char * temp = (unsigned char *) & a; Que es, la fundición de la dirección de a a unsigned char*, no el valor de a. Estoy en lo cierto?
    • si usted encuentra un error tipográfico, ir y arreglar!
    • Yo no soy seguro si que es una errata o que eso significaba. Si eso significaba, entonces tengo que entiendo correctamente, en lugar de solucionarlo (porque no hay nada que arreglar). Así que me ayude a entender todo este asunto de la UB y estricto-aliasing!
    • Oops. Ignoren mis dos anteriores comentarios. Quise decir que cuando temp[3] &= 0x7f;, no cambia nada en temp_float que el retorno de la función (temp_float es un copiar, se puede cambiar cuando se cambia el objeto original). Así que usted piensa que la función hace lo que se espera que haga?

  2. 30

    Mi favorito personal indefinido comportamiento es que si no está vacío archivo de origen no termina en una línea nueva, el comportamiento es indefinido.

    Sospecho que es cierto, sin embargo que no compilador voy a ver alguna vez ha tratado de un archivo de origen de manera diferente de acuerdo a si es o no es newline terminado, otros que para emitir una advertencia. Así que no es realmente algo que te va a sorprender conscientes de los programadores, otros que no podían ser sorprendido por la advertencia.

    Así que para los auténticos problemas de portabilidad (que en su mayoría son dependiente de la implementación, en lugar de indeterminado o indefinido, pero creo que cae en el espíritu de la pregunta):

    • char no es necesariamente (onu)firmaron.
    • int puede ser de cualquier tamaño de 16 bits.
    • flotadores no son necesariamente IEEE o con formato compatible.
    • tipos enteros no son necesariamente complemento a dos, y entero de desbordamiento aritmético provoca un comportamiento indefinido (hardware moderno no se cuelgue, pero algunas optimizaciones del compilador dará como resultado un comportamiento diferente de la envolvente a pesar de que es lo que el hardware no. Por ejemplo if (x+1 < x) puede ser optimizado como siempre falso cuando x ha firmado tipo: ver -fstrict-overflow opción en GCC).
    • «/», «.» y «..» en un #include tienen ningún significado definido y pueden ser tratados de manera diferente por diferentes compiladores (esto en realidad varían, y si va mal va a arruinar su día).

    Realmente grave que puede resultar sorprendente incluso en la plataforma que desarrollamos, porque el comportamiento es sólo parcialmente indefinido /no especificado:

    • POSIX roscado y ANSI el modelo de memoria. El acceso concurrente de memoria no está tan bien definido como el de los novatos de pensar. volátiles de no hacer lo que los novatos piensan. Orden de accesos a la memoria no está tan bien definido como el de los novatos de pensar. Accesos puede se mueve a través de barreras de memoria en ciertas direcciones. Memoria de coherencia de caché no es necesario.

    • De perfiles código no es tan fácil como usted piensa. Si el bucle de prueba no tiene ningún efecto, el compilador puede eliminar parte o la totalidad de ella. en línea no se ha definido en efecto.

    Y, como creo que Nils menciona:

    • VIOLAR LA ESTRICTA ALIASING REGLA.
    • Steve – me encontré exactamente lo que usted describe (el final newline problema) en la década de los 90 w/ el Microtec compilador para la familia 68K. Pensé que la herramienta fue buggy, pero acabo de añadir la nueva línea «para evitar que el estúpido de la herramienta». A diferencia de mi exceso de confianza compañero de trabajo (ver mi otro comentario sobre este tema), yo no estaba tan cocksure que me gustaría escribir un reporte de defectos… lo bueno es que no.
    • Firmado desbordamiento de enteros ser indefinido no es pedante; al menos GCC se aplica optimizaciones en el supuesto de que nunca pasa, tales como » si (a + 1 > a)’ siempre pasa y nunca la detección de envolvente.
    • Yo no tengo ningún problema con desbordamientos de enteros ceder parcialmente indeterminado de valores, lo cual sería suficiente semántica para justificar GCC optimización en el referido caso. Desafortunadamente, algunos compilador escritores parecen pensar de desbordamiento de enteros deberían anular las leyes del tiempo y de la causalidad (tiempo yo tal vez podría vivir con el, si el código fue reordenar en el supuesto de que no se desborde; la negación de la causalidad en mi humilde opinión debería ser considerado como la locura, pero por desgracia no todo el mundo está de acuerdo.)
  3. 22

    Dividir algo por un puntero a algo. Simplemente no voy a compilar por alguna razón… 🙂

    result = x/*y;
    • resultado = x/(*y) debería funcionar.
    • Jaja bueno, yo soy la escritura hacia abajo 🙂
    • porque ‘/*’ está amenazada, como comentario, sólo tiene que añadir un espacio entre ‘/’ y’*’, y se debería funciona (al menos que funcione en mi gcc 8.1.1).
  4. 19

    Mi favorito es este:

    //what does this do?
    x = x++;

    Para responder a algunos comentarios, no se ha definido el comportamiento de acuerdo a la norma. Al ver esto, el compilador puede hacer nada hasta e incluyendo el formato de su unidad de disco duro.
    Véase, por ejemplo,este comentario aquí. El punto no es que usted puede ver que hay una posible expectativa razonable de algunos comportamientos. Debido a que el estándar de C++ y la forma en que la secuencia de los puntos se definen, esta línea de código es en realidad un comportamiento indefinido.

    Por ejemplo, si tuviéramos x = 1 antes de la línea de arriba, a continuación, ¿cuál sería el resultado válido será después? Alguien comentó que se debe

    x se incrementa en 1

    así que debemos ver x == 2 después. Sin embargo, esto no es realmente cierto, usted encontrará algunos compiladores que tiene x == 1 luego, o tal vez incluso x == 3. Usted tendría que mirar de cerca el ensamblado generado para ver por qué esto podría ser, pero las diferencias son debido a que el problema subyacente. Esencialmente, creo que esto es porque el compilador es permitido evaluar las dos asignaciones de declaraciones en cualquier orden que le gusta, por lo que podría hacer el x++ primero, o el x = primera.

    • X se incrementa en 1. Se asigna x a sí mismo y, a continuación, incremmented ella. es equivalente a x++;
    • esto hace que x = x; x += 1; entonces sí, como charles dice graham. Yo no llamaría a esto no especificado
    • La modificación de una variable más de una vez entre los dos puntos de secuencia que se indica explícitamente como un comportamiento indefinido en tanto estándar de C y C++.
    • Estoy de grietas de hasta riendo ahora mismo en el pensamiento de alguien que escribir un compilador de C que formatea la unidad de disco duro al ver que x = x++ porque es definido en la norma 🙂
    • +1, especialmente para el «formato de disco duro de parte». En realidad, para la gente que un código como este, formatear el disco duro podría salvar a las futuras generaciones de mantenimiento a los programadores una gran cantidad de dolor…
    • 2 cosas: 1) es absolutamente un comportamiento indefinido; hace unos 15 años yo iba a debatir con alguien en mi grupo que escribió un informe de defecto a los proveedores del compilador (¡caramba!) cuando escribió este código exacto (excepto él usa «yo» en lugar de «x») y la «i» se encuentra atrapada en 1; y 2) me reí cuando leí la parte de formatear el disco duro, probablemente porque ese es el tipo de cosa que yo diría que demasiado.
    • Yo diría que x se incrementa, entonces se asigna con el valor anterior, debido a que x++ devuelve y que tiene prioridad sobre la asignación. Pero sí es indefinido… Como muchas cosas en el lenguaje (haciendo un montón de dolores de cabeza…)

  5. 10

    Otro problema que he encontrado (que se define, pero definitivamente inesperado).

    char es el mal.

    • firmado o sin firmar dependiendo de lo que el compilador se siente
    • no mandato como 8 bits
    • Bueno, no es malo si la usas para lo que está destinado, es decir, para que personajes
    • En realidad, hay tres diferentes tipos de carácter: char, unsigned char y signed char. Son explícitamente tipos distintos.
    • Que debe uso (punteros a o series de llano) char cuando se trata con cadenas. Muchas funciones de biblioteca estándar (como todos los str* funciones ()) toman punteros a char y dándoles otra cosa requiere feo de moldes.
    • ¿Quién dijo algo acerca de las cadenas ? Incorporado a los programadores, a veces, jugar con tamaño variable por eficiencia. Asumiendo que nada acerca de char no funciona de la cruz-plataforma. Llamar a funciones de la biblioteca destinados a las cadenas, sino que se define cuando una cadena era simplemente un char* Unicode y no habían sido inventados pueden estar bien, pero si voy a ser franco… no escribir programas para, al menos, compatible con caracteres unicode es estúpido
  6. 8

    No puedo contar el número de veces que he corregido printf especificadores de formato para que coincida con su argumento. Cualquier desajuste es un comportamiento indefinido.

    • No, usted no debe pasar a un int (o long) a %x – un unsigned int es necesario
    • No, usted no debe pasar a un unsigned int a %d – un int es necesario
    • No, usted no debe pasar a un size_t a %u o %d uso %zu
    • No, usted no debe imprimir un puntero con %d o %x uso %p y arrojado a un void *
    • La norma implica (en un no-normativos de la nota de pie de página) que la aprobación de un int a %x, o un unsigned int a %d, está bien, siempre y cuando el valor está dentro del rango de ambos tipos. Aún así, yo prefiero evitarlo.
  7. 7

    Un compilador no tiene que decirle a usted que usted está llamando a una función con un número incorrecto de parámetros/parámetro incorrecto tipos si el prototipo de la función no está disponible.

    • Sí. Benevolente compiladores sin embargo por lo general le ayudará con una advertencia…
    • Como de C99, llamar a una función y no se observan declaración requiere de un diagnóstico. Esa declaración no tenía para ser un prototipo (es decir, una declaración en la que especifica los tipos de los parámetros), pero siempre debe ser. (Variadic funciones como printf todavía puede ser problemático.)
  8. 6

    He visto un montón de relativamente inexpertos programadores mordido por varios caracteres constantes.

    Este:

    "x"

    es un literal de cadena (lo cual es de tipo char[2] y se desintegra a char* en la mayoría de los contextos).

    Este:

    'x'

    es una corriente constante de caracteres (que, por razones históricas, es de tipo int).

    Este:

    'xy'

    también es perfectamente legal de carácter constante, pero su valor (que es todavía de tipo int) es de aplicación definido. Es casi inútil característica del lenguaje que sirve principalmente a causa de la confusión.

    • Es útil cuando se escribe C en el Macintosh, que con frecuencia se utiliza un entero de 32 bits para sostener cuatro caracteres tipos de archivo, la aplicación de firmas, etc., aunque trigraphs sería bastante desagradable lío '????'.
    • Esto es especialmente peligroso con funciones sobrecargadas que tomar en char* así como char. He visto a muchas personas mordido por ella (example)
    • La pregunta es acerca de C, no C++. No hay funciones sobrecargadas.
  9. 4

    El ruido de los desarrolladores publicado algunos grandes ejemplos hace un tiempo, en un post cada programador de C debería leer. Algunos interesantes no mencionado antes:

    • Firmado desbordamiento de entero – no, no es ok para ajustar una variable firmada pasado su máximo.
    • Desreferenciar un Puntero NULL – sí, esto es indefinido, y pueden ser ignorados, véase la parte 2 de el enlace.
  10. 1

    Asegúrese siempre de inicializar las variables antes de usarlas! Cuando yo acababa de empezar con C, que me causó una serie de dolores de cabeza.

  11. 0

    El uso de la macro versiones de funciones como «max» o «isupper». Las macros evaluar sus argumentos dos veces, así que usted conseguirá efectos secundarios inesperados cuando se llama max(++i, j), o isupper(*p++)

    El de arriba es para el nivel C. En C++, estos problemas han desaparecido en gran medida. La función max es ahora una función de plantilla.

    • onebyone, si es UB o no, depende de la aplicación de aquellos. si se trata de un > b ? a : b; entonces no es. pero si sucede que el uso de un o b entre dos puntos de secuencia más de una vez, entonces es UB (con a o b, siendo ++i)
  12. -1

    olvidar añadir static float foo(); en el archivo de encabezado, sólo para obtener excepciones de punto flotante se produce cuando volvería 0.0 f;

    • ¿Por qué es eso así? Podría usted comentar?
    • ¿Por qué se declara una static función en un archivo de encabezado?

Dejar respuesta

Please enter your comment!
Please enter your name here