¿Cuáles son los comunes indefinido/sin especificar el comportamiento de C que se ejecuta en?[cerrado]

StackOverflow https://stackoverflow.com/questions/98340

  •  01-07-2019
  •  | 
  •  

Pregunta

Un ejemplo de comportamientos no especificados 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?

¿Fue útil?

Solución

Un lenguaje abogado de que se trate.Hmkay.

Personal de mi top3:

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

    :-)

Editar He aquí un pequeño ejemplo que hace mal dos veces:

(suponga 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).

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 de la forma: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;
}

Otros consejos

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.
  • los flotadores no son necesariamente IEEE o con formato compatible.
  • los 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 graves que puede ser 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 movió a través de barreras de memoria en ciertas direcciones.Memoria de coherencia de caché no es necesario.

  • Código de generación de perfiles 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.

Dividir algo por un puntero a algo.Simplemente no voy a compilar por alguna razón...:-)

result = x/*y;

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 hemos tenido 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++ en primer lugar, o el x = en primer lugar.

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

char es el mal.

  • con o sin signo, dependiendo de lo que el compilador se siente
  • no el mandato de 8 bits

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 se requiere
  • No, usted no debe pasar a un unsigned int a %d - un int se requiere
  • 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 *

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.

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.

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.

La EE aquí acaba de descubrir que un>>-2 es un poco tensa.

Asentí con la cabeza y les dijo que no era natural.

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.

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 estándar de C.En C++, estos problemas han desaparecido en gran medida.La función max es ahora una función de plantilla.

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;

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top