¿Por qué llamar a malloc () no hace una diferencia?

Aquí hay un ejemplo básico:

#include  int main(void) { char *name = (char *) malloc(2 * sizeof(char)); if(name == NULL) { fprintf(stderr, "Error: Unable to allocate enough memory!\n"); return EXIT_FAILURE; } strcpy(name, "Bob Smith"); printf("Name: %s\n", name); free(name); return EXIT_SUCCESS; } 

Debido a que solo estoy asignando 2 bytes de información (2 caracteres), debería haber algún tipo de error cuando ejecuto strcpy, ¿verdad? Esto no sucede, en lugar de eso, simplemente copia la cadena, la imprime, libera la memoria y sale con éxito. ¿Por qué sucede esto y cómo puedo usar malloc correctamente?

Su progtwig invoca un comportamiento indefinido .

El comportamiento indefinido es un comportamiento que está fuera de la especificación del lenguaje. Por definición, esto significa que no se garantiza que tenga ningún tipo de comportamiento bien definido (como un error). El progtwig es explícitamente inválido.

Cuando usa strcpy , la función simplemente asume que el búfer que le pasa es lo suficientemente grande como para contener la cadena que desea copiar. Si la suposición es incorrecta, intenta escribir en un área fuera del búfer. Y si esto sucede, el progtwig cae en este caso de la especificación C, en J.2 Comportamiento indefinido :

El comportamiento no está definido en las siguientes circunstancias:

  • La adición o resta de un puntero a, o más allá de, un objeto de matriz y un tipo entero produce un resultado que no apunta hacia, o más allá, el mismo objeto de matriz

Por lo tanto, para usar strcpy correctamente, debe asegurarse manualmente de que las suposiciones anteriores sobre la longitud de la cadena y la longitud del búfer se mantienen. Para hacerlo, una forma fácil es mantener la longitud del búfer guardada en algún lugar, calcular la longitud de la cadena que desea copiar y compararlas.

Por ejemplo:

 #include  #include  #include  int main(void) { size_t bufferSize = 2 * sizeof(char); char *name = malloc(bufferSize); if(name == NULL) { fprintf(stderr, "Error: Unable to allocate enough memory!\n"); return EXIT_FAILURE; } size_t length = strlen("Bob Smith"); if(length + 1 > bufferSize) { fprintf(stderr, "Error: The target buffer is too small!\n"); return EXIT_FAILURE; } strcpy(name, "Bob Smith"); printf("Name: %s\n", name); free(name); return EXIT_SUCCESS; } 

Como nota al margen no relacionada, notará que no lancé el resultado de malloc , porque un void* es implícitamente convertible en char* .


Como nota final:

Este aspecto de C puede parecer poco práctico cuando intenta asegurarse de que su código sea correcto (ya sea porque está aprendiendo el idioma o porque tiene la intención de lanzar el software).

Es por esto que hay algunas herramientas que ofrecen darle un error cada vez que su progtwig hace algo inválido. Valgrind es una de esas herramientas (como mencionó Jonathan Leffler en los comentarios).

Obtendrá un informe de errores si lo comstack y lo ejecuta con AddressSanitizer :

 $ gcc -g ac -Wall -Wextra -fsanitize=address $ ./a.out ================================================================= ==3362==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000eff2 at pc 0x7f9ff2b02dc4 bp 0x7fffe9190650 sp 0x7fffe918fdf8 WRITE of size 10 at 0x60200000eff2 thread T0 #0 0x7f9ff2b02dc3 in __asan_memcpy (/lib64/libasan.so.2+0x8cdc3) #1 0x4009df in main /home/m/ac:11 #2 0x7f9ff26d678f in __libc_start_main (/lib64/libc.so.6+0x2078f) #3 0x400898 in _start (/home/m/a.out+0x400898) 0x60200000eff2 is located 0 bytes to the right of 2-byte region [0x60200000eff0,0x60200000eff2) allocated by thread T0 here: #0 0x7f9ff2b0ea0a in malloc (/lib64/libasan.so.2+0x98a0a) #1 0x400977 in main /home/m/aa.c:6 #2 0x7f9ff26d678f in __libc_start_main (/lib64/libc.so.6+0x2078f) SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 __asan_memcpy 
  1. malloc devolverá el null si no se pudo asignar la memoria, por ejemplo, su sistema no tiene memoria. ¡Eso es poco probable para 2 bytes!
  2. Si copia más bytes de los que asignó, se obtiene un comportamiento indefinido. ¡Y ese comportamiento indefinido puede ser que su progtwig se comporte como se espera!
  3. Como nota más general sobre el uso “correcto” de malloc que pregunta, recomendaría char *name = malloc(2 * sizeof(*name)); . Es más conciso, no ocultará un error si olvida incluir stdlib.h y será más fácil cambiar el tipo de name en el futuro.
  4. Con respecto al uso seguro de strcpy no debe reemplazarlo con strncpy ya que no es seguro si el búfer no es lo suficientemente grande (no termina en nulo) y es potencialmente ineficiente. Compruebe si su sistema tiene strcpy_s o strlcpy .

Hay suficientes respuestas, intentaré llevarlas a un nivel más básico y le daré lo siguiente como la causa principal:

C no incluye ninguna comprobación de límites en absoluto.

El beneficio es que el tiempo de ejecución de C es muy pequeño y eficiente. El retroceso es, a menudo no recibirá ningún mensaje de error por cosas como lo hizo en su pregunta … solo (quizás mucho después del error) un comportamiento incorrecto o incluso lockings.

¿Por qué puedes escribir más datos en un búfer que malloc() que el tamaño del búfer? Aparte del hecho de que no puede predecir los resultados de un comportamiento indefinido, en realidad hay una explicación de por qué a veces parece totalmente seguro escribir más bytes en un búfer malloc() que el número de bytes que pidió. para.

Esto se debe a las implicaciones de los requisitos establecidos en la Sección 7.20.3 Funciones de administración de memoria del estándar C :

El orden y la continuidad del almacenamiento asignado por las llamadas sucesivas a las funciones calloc , malloc y realloc no están especificados. El puntero devuelto si la asignación es exitosa se alinea adecuadamente para que pueda ser asignado a un puntero a cualquier tipo de objeto y luego se use para acceder a dicho objeto o una matriz de dichos objetos en el espacio asignado (hasta que el espacio esté desasignado explícitamente) .

Tenga en cuenta el texto en cursiva: “El puntero devuelto si la asignación se realiza correctamente se alinea adecuadamente para que pueda asignarse a un puntero a cualquier tipo de objeto”.

Esas restricciones de alineación significan que malloc() y las funciones relacionadas deben efectivamente entregar la memoria en trozos alineados, y cualquier llamada exitosa a malloc() probablemente devolverá la memoria que está en múltiplos exactos de las restricciones de alineación que malloc() está operando debajo.

En una máquina x86, cuyo IIRC tiene una restricción de alineación de 8 bytes, una llamada como malloc( 11 ) probablemente devolverá un puntero a un búfer que en realidad es de 16 bytes.

Esa es una de las razones por las que sobrescribir el final de un búfer malloc() parece ser inofensivo.

Bueno strcpy(name, "Bob Smith"); invocará un comportamiento indefinido . name no es suficiente para almacenar "Bob Smith" . La solución sería …

  char a[]="Bob Smith"; char *name = malloc(strlen(a)+1); //you should not cast return of malloc if(name == NULL) { fprintf(stderr, "Error: Unable to allocate enough memory!\n"); return EXIT_FAILURE; } strncpy(name,a,strlen(a)); 

tratar:

 strncpy(name, "Bob Smith", 2 * sizeof(char));