studio reales proyectos para libro introducción incluye herramientas fuente español desarrollo código con avanzado aplicaciones c compiler-construction

c - reales - libro de android studio en español pdf



¿Cuál es el comportamiento indefinido/no especificado común para C con el que te encuentras? (13)

Un ejemplo de comportamiento no especificado en el lenguaje C es el orden de evaluación de argumentos para una función. Puede ser de izquierda a derecha o de derecha a izquierda, simplemente no lo sabes. Esto afectaría cómo se evalúa foo(c++, c) o foo(++c, c) .

¿Qué otro comportamiento no especificado existe que pueda sorprender al programador desprevenido?


¡Asegúrese de inicializar siempre sus variables antes de usarlas! Cuando recién comencé con C, eso me causó muchos dolores de cabeza.


El EE aquí acaba de descubrir que a >> - 2 es un poco cargado.

Asentí y les dije que no era natural.


Mi favorito es este:

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

Para responder algunos comentarios, es un comportamiento indefinido según el estándar. Al ver esto, el compilador puede hacer cualquier cosa e incluso formatear su disco duro. Ver por ejemplo este comentario aquí . El punto no es que pueda ver que hay una posible expectativa razonable de algún comportamiento. Debido al estándar C ++ y la forma en que se definen los puntos de secuencia, esta línea de código es en realidad un comportamiento indefinido.

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

x se incrementa en 1

entonces deberíamos ver x == 2 después. Sin embargo, esto no es cierto en realidad, encontrará algunos compiladores que tienen x == 1 después, o tal vez incluso x == 3. Tendría que observar detenidamente el ensamblaje generado para ver por qué podría ser así, pero las diferencias se deben al problema subyacente. Esencialmente, creo que esto se debe a que el compilador puede evaluar las dos instrucciones de asignación en el orden que quiera, por lo que podría hacer primero con x++ o con x = primero.


Otro problema que encontré (que está definido, pero definitivamente inesperado).

char es malvado

  • firmado o sin firmar según lo que el compilador sienta
  • no obligatorio como 8 bits

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


Usando las versiones de macro de funciones como "max" o "isupper". Las macros evalúan sus argumentos dos veces, por lo que obtendrá efectos secundarios inesperados cuando llame a max (++ i, j) o isupper (* p ++)

Lo anterior es para el estándar C. En C ++ estos problemas han desaparecido en gran medida. La función máxima ahora es una función de plantilla.


olvidándose de agregar static float foo(); en el archivo de encabezado, solo para lanzar excepciones de coma flotante cuando devolvería 0.0f;


Los desarrolladores de clang publicaron algunos buenos ejemplos hace un tiempo, en una publicación que todo programador de C debe leer. Algunos interesantes que no se mencionaron anteriormente:

  • Desbordamiento de enteros con signo: no, no está bien para envolver una variable con signo más allá de su máximo.
  • Desreferenciando un puntero NULL - sí esto no está definido, y puede ser ignorado, vea la parte 2 del enlace.

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

result = x/*y;


He visto a muchos programadores relativamente inexpertos mordidos por constantes de múltiples caracteres.

Esta:

"x"

es un literal de cadena (que es del tipo char[2] y decae a char* en la mayoría de los contextos).

Esta:

''x''

es una constante de carácter ordinario (que, por razones históricas, es de tipo int ).

Esta:

''xy''

también es una constante de carácter perfectamente legal, pero su valor (que todavía es de tipo int ) está definido por la implementación. Es una función de lenguaje casi inútil que sirve principalmente para causar confusión.


No puedo contar la cantidad de veces que he corregido los especificadores de formato printf para que coincidan con su argumento. Cualquier desajuste es un comportamiento indefinido .

  • No, no debe pasar un int (o long ) a %x - se requiere un unsigned int
  • No, no debe pasar una unsigned int a %d - se requiere int
  • No, no debes pasar size_t a %u o %d - usa %zu
  • No, no debes imprimir un puntero con %d o %x - usa %p y lanza a un void *

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

Sospecho que es cierto, sin embargo, que ningún compilador que alguna vez haya visto ha tratado un archivo fuente de manera diferente según haya terminado o no la nueva línea, salvo para emitir una advertencia. Por lo tanto, no es algo que sorprenda a los programadores que desconocen, aparte de que podrían sorprenderse con la advertencia.

Por lo tanto, para problemas de portabilidad genuinos (que en su mayoría dependen de la implementación en lugar de no especificados o indefinidos, pero creo que eso encaja en el espíritu de la pregunta):

  • char no es necesariamente (no) firmado.
  • int puede ser de cualquier tamaño desde 16 bits.
  • los flotantes no tienen necesariamente formato IEEE o son conformes.
  • los tipos enteros no son necesariamente complemento de dos, y el desbordamiento aritmético entero causa un comportamiento indefinido (el hardware moderno no se bloquea, pero algunas optimizaciones del compilador darán como resultado un comportamiento diferente del envolvente aunque eso es lo que hace el hardware. Por ejemplo, if (x+1 < x) puede optimizarse como siempre falso cuando x tiene el tipo firmado: vea la opción -fstrict-overflow en GCC).
  • "/", "." y ".." en un #include no tienen un significado definido y pueden ser tratados de forma diferente por diferentes compiladores (esto realmente varía, y si sale mal arruinará su día).

Serios realmente serios que pueden sorprender incluso en la plataforma en la que se desarrolló, porque el comportamiento solo está parcialmente indefinido / no especificado:

  • Roscado POSIX y el modelo de memoria ANSI. El acceso concurrente a la memoria no está tan bien definido como los principiantes piensan. volátil no hace lo que los novatos piensan. Los accesos a la memoria no están tan bien definidos como los principiantes piensan. Los accesos se pueden mover a través de barreras de memoria en ciertas direcciones. La coherencia del caché de memoria no es necesaria.

  • El código de perfil no es tan fácil como crees. Si su bucle de prueba no tiene ningún efecto, el compilador puede eliminar una parte o la totalidad. en línea no tiene un efecto definido.

Y, como creo, Nils mencionó de paso:

  • VIOLANDO LA ESTRICTA REGLA DE ALIAS.

Una pregunta de abogado de lenguaje. Hmkay.

Mi top3 personal:

  1. violando la regla de aliasing estricta
  2. violando la regla de aliasing estricta
  3. violando la regla de aliasing estricta

    :-)

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

(supongamos 32 bitts y little endian)

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

Ese código trata de obtener el valor absoluto de un flotante mediante el intercambio de bits con el bit de signo directamente en la representación de un flotador.

Sin embargo, el resultado de crear un puntero a un objeto mediante conversión de un tipo a otro no es válido C. El compilador puede suponer que los punteros a diferentes tipos no apuntan al mismo bloque de memoria. Esto es cierto para todo tipo de punteros excepto void * y char * (sign-ness no importa).

En el caso anterior, lo hago dos veces. Una vez para obtener un alias int para el float a, y una vez para convertir el valor a float.

Hay tres formas válidas de hacer lo mismo.

Use un puntero char o void durante el lanzamiento. Estos siempre se parecen a cualquier cosa, por lo que están 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; }

Usa memcopy. Memcpy toma punteros vacíos, por lo que también forzará el aliasing.

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 forma válida: usar uniones. Esto no está explícitamente definido desde 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; }