Comportamiento indefinido de C (Undefined behavior)

En el lenguaje C,"undefined behavior" (comportamiento indefinido)se refiere a que el comportamiento del programa no está claramente definido en el estándar del lenguaje C, por lo que puede manifestarse como cualquier resultado.

Esto significa que cuando un programa presenta un comportamiento indefinido, puede producir resultados impredecibles, incluyendo fallos del programa, corrupción de datos, vulnerabilidades de seguridad, e incluso puede parecer que funciona con normalidad.

El comportamiento indefinido es un concepto importante en el lenguaje C, porque afecta a la corrección y seguridad del programa.

A continuación se presentan algunas situaciones comunes que pueden provocar un comportamiento indefinido:

Acceso fuera de los límites del array

Cuando intentamos acceder a un elemento fuera de los límites del array, es decir, a un elemento antes del elemento 0 o después de la longitud del array, el compilador no puede determinar qué contenido se almacena en el espacio de memoria al que se accede, lo que provoca un comportamiento indefinido. Por ejemplo:

int arr[3] = {1, 2, 3};
printf("%d\n", arr[5]); // 越界访问,结果未定义

Desreferenciar puntero nulo

Cuando intentamos realizar una operación de desreferencia sobre un puntero nulo, el compilador no puede determinar el contenido almacenado en el espacio de memoria al que se quiere acceder, lo que provoca un comportamiento indefinido. Por ejemplo:

int *ptr = NULL;
printf("%d\n", *ptr); // 解引用空指针,结果未定义

Variables locales no inicializadas

Cuando usamos una variable local no inicializada, su valor no está definido, lo que provoca un comportamiento indefinido. Por ejemplo:

int x;
printf("%d\n", x); // x 未初始化,结果未定义

División de números de punto flotante por cero

Cuando intentamos dividir un número de punto flotante por cero, el resultado no está definido. Por ejemplo:

float x = 1.0;
float y = x / 0.0; // 浮点数除以零,结果未定义

División de enteros por cero

Cuando intentamos dividir un entero por cero, el resultado no está definido. Por ejemplo:

int x = 10;
int y = x / 0; // 整数除以零,结果未定义

Desbordamiento de signo

Cuando una operación aritmética con enteros hace que el resultado supere el rango que puede representar el tipo entero, el resultado no está definido. Por ejemplo:

signed char x = 127;
x = x + 1; // signed char 溢出,结果未定义

Operando de desplazamiento demasiado grande

Al realizar una operación de desplazamiento, si el número de bits desplazados es mayor o igual que el número de bits del operando, el resultado no está definido. Por ejemplo:

int x = 1;
int y = x << 32; // 位移操作数太大,结果未定义

Conversión de tipos incorrecta

Cuando realizamos una conversión de tipos insegura, el resultado no está definido. Por ejemplo:

int *ptr = (int *)malloc(sizeof(int));
float *fptr = (float *)ptr; // 错误的类型转换,结果未定义

Fuera de los límites de memoria

Cuando escribimos datos en memoria que ya ha sido liberada o no ha sido asignada, el resultado no está definido. Por ejemplo:

int *ptr = (int *)malloc(sizeof(int));
free(ptr);
*ptr = 10; // 内存越界,结果未定义

Comportamiento indefinido de punto flotante

Por ejemplo, comparar si dos valores NaN (no numéricos) son iguales, esto es un comportamiento indefinido. Por ejemplo:

float x = sqrt(-1);
float y = sqrt(-1);
if (x == y) {
    printf("NaN values are equal\n");
}

Otros

Hay otros comportamientos indefinidos:

  • Uso de características de punto flotante no definidas:Depender de comportamientos de punto flotante específicos del hardware o de la implementación, como la precisión o el redondeo de los números de punto flotante.

  • Desajuste en el número de parámetros de una función:El número de argumentos proporcionados al llamar a una función no coincide con la definición de la función, comoprintf("%s %d", "Name")。

  • Modificar literales de cadena:Intentar modificar el contenido de un literal de cadena, comochar *str = "Hello"; str[0] = 'h';。

  • Uso de estados de programa no definidos:Depender de estados de programa no definidos, como el valor inicial de una variable global.

  • Violación de reglas sintácticas estrictas:Violar las reglas sintácticas estrictas del lenguaje C, como usar identificadores no declarados.

  • Condiciones de carrera en múltiples hilos:En entornos de múltiples hilos, el acceso no sincronizado a recursos compartidos puede provocar un comportamiento indefinido.

  • Uso de comportamientos no definidos de funciones de la biblioteca estándar:El comportamiento de algunas funciones de la biblioteca estándar en condiciones específicas puede no estar definido, como el comportamiento de fscanf() cuando no coincide con ninguna entrada.

Estas son situaciones que deben evitarse en el proceso de programación, porque pueden hacer que el programa produzca comportamientos inciertos en diferentes entornos, lo que hace que el código no sea portable y puede provocar errores en el programa.

Cómo evitarlo

Para evitar el comportamiento indefinido, durante el proceso de desarrollo necesitamos:

  • Leer y cumplir cuidadosamente el estándar del lenguaje C: conocer qué operaciones pueden provocar un comportamiento indefinido y evitar esas operaciones.
  • Usar herramientas de análisis estático: estas herramientas pueden ayudar a detectar posibles comportamientos indefinidos.
  • Realizar pruebas exhaustivas: probar diferentes rutas de ejecución del programa para garantizar que el programa funcione correctamente en diversas situaciones.
  • Evitar depender del comportamiento indefinido: no asumir que el comportamiento indefinido producirá un resultado específico.
  • Usar funciones y bibliotecas seguras: utilizar las funciones bien definidas que proporciona la biblioteca estándar, evitando funciones no estándar o inseguras que puedan provocar un comportamiento indefinido.

El comportamiento indefinido es un concepto complejo y peligroso en el lenguaje C; requiere que los desarrolladores tengan un conocimiento profundo de las reglas del lenguaje C y tomen las medidas adecuadas para evitarlo. Siguiendo las mejores prácticas y utilizando las herramientas apropiadas, se puede minimizar el riesgo de comportamiento indefinido, mejorando así la fiabilidad y la seguridad del programa.

Otras extensiones