Específicamente, ¿qué tiene de peligroso lanzar el resultado de malloc?

86

Ahora, antes de que la gente comience a marcar esto como dup, he leído todo lo siguiente, ninguno de los cuales proporciona la respuesta que estoy buscando:

  1. Preguntas frecuentes de C: ¿Qué hay de malo en emitir el valor de retorno de malloc?
  2. SO: ¿Debería lanzar explícitamente el valor de retorno de malloc ()?
  3. SO: Pointer-casts innecesarios en C
  4. SO: ¿Lanzo el resultado de malloc?

Tanto las preguntas frecuentes de C como muchas respuestas a las preguntas anteriores citan un misterioso error que mallocel valor de retorno del casting puede ocultar; sin embargo, ninguno de ellos da un ejemplo específico de tal error en la práctica. Ahora preste atención a que dije error , no advertencia .

Ahora dado el siguiente código:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

Compilar el código anterior con gcc 4.2, con y sin la conversión, da las mismas advertencias, y el programa se ejecuta correctamente y proporciona los mismos resultados en ambos casos.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

Entonces, ¿alguien puede dar un ejemplo de código específico de un error de compilación o tiempo de ejecución que podría ocurrir debido al mallocvalor de retorno del casting , o es solo una leyenda urbana?

Editar Me he encontrado con dos argumentos bien escritos sobre este tema:

  1. A favor de la conversión: Aviso de CERT: Transmita inmediatamente el resultado de una llamada a la función de asignación de memoria en un puntero al tipo asignado
  2. Against Casting (error 404 de 2012-02-14: use la copia de Internet Archive Wayback Machine del 2010-01-27. {2016-03-18: "La página no se puede rastrear ni mostrar debido a robots.txt."})
Robert S. Barnes
fuente
6
La conversión de voidpunteros permite compilar el código como C ++; algunas personas dicen que es una característica, yo diría que es un error;)
Christoph
1
Además, lea los comentarios al primero de sus enlaces, ya que describe lo que debe hacer en lugar de transmitir: securecoding.cert.org/confluence/display/seccode/…
Christoph
3
Seguiré el consejo de CERT para incluir al elenco. Además, no olvidaré incluir stdlib.h, nunca. :)
Abhinav
1
Aquí hay un SO-ejemplo de un error en tiempo de ejecución de compilación debido al mallocvalor de retorno de la conversión: conversión a int*un arco de 64 bits.
John_West
1
esta pregunta se etiqueta Cno C++(son dos idiomas diferentes) Así que cualquier discusión (como en algunas de las respuestas) no es relevante para esta pregunta.
user3629249

Respuestas:

66

No obtendrá un error del compilador , sino una advertencia del compilador . Como dicen las fuentes que cita (especialmente la primera ), puede obtener un error de tiempo de ejecución impredecible al usar el elenco sin incluirstdlib.h .

Entonces, el error de su lado no es el elenco, sino el olvido de incluir stdlib.h. Los compiladores pueden asumir que malloces una función que regresa int, por lo tanto, convierten el void*puntero realmente devuelto por mallocay intluego a su tipo de puntero debido a la conversión explícita. En algunas plataformas, los intpunteros pueden ocupar diferentes números de bytes, por lo que las conversiones de tipos pueden provocar daños en los datos.

Afortunadamente, los compiladores modernos dan advertencias que apuntan a su error real. Vea el gccresultado que proporcionó: le advierte que la declaración implícita ( int malloc(int)) es incompatible con la declaración integrada malloc. Por eso gccparece saberlo mallocincluso sin él stdlib.h.

Dejar el elenco para evitar este error es básicamente el mismo razonamiento que escribir

if (0 == my_var)

en vez de

if (my_var == 0)

ya que el último podría conducir a un error grave si uno confundiera =y ==, mientras que el primero conduciría a un error de compilación. Personalmente, prefiero este último estilo, ya que refleja mejor mi intención y no suelo cometer este error.

Lo mismo ocurre con la conversión del valor devuelto por malloc: Prefiero ser explícito en la programación y generalmente verifico para incluir los archivos de encabezado para todas las funciones que uso.

Ferdinand Beyer
fuente
2
Parecería que dado que el compilador advierte sobre la declaración implícita incompatible, esto no es un problema siempre y cuando preste atención a las advertencias del compilador.
Robert S. Barnes
4
@Robert: sí, dadas ciertas suposiciones sobre el compilador. Cuando las personas dan consejos sobre la mejor manera de escribir C en general , no pueden asumir que la persona que recibe el consejo está usando una versión reciente de gcc.
Steve Jessop
4
Ah, y la respuesta a la segunda pregunta es que la persona que llama contiene el código para recoger el valor de retorno (que cree que es un int) y convertirlo a T *. El destinatario simplemente escribe el valor de retorno (como un vacío *) y regresa. Entonces, dependiendo de la convención de llamada: int return y void * returns pueden o no estar en el "mismo lugar" (registro o ranura de pila); int y void * pueden tener o no el mismo tamaño; convertir entre los dos puede o no ser una operación no operativa. Por lo tanto, podría "simplemente funcionar", o el valor podría estar dañado (algunos bits se perderían, quizás), o la persona que llama podría captar completamente el valor incorrecto.
Steve Jessop
1
@ RobertS.Barnes llega tarde a la fiesta, pero: El valor de retorno generalmente no es parte de la firma de la función, ni siquiera en C ++. El enlazador solo genera un salto a un símbolo, eso es todo.
Peter - Reincorpora a Monica el
3
Puede obtener un error de tiempo de ejecución impredecible al usar la transmisión sin incluir stdlib.h . Eso es cierto, pero no incluirlo stdlib.hya es un error en sí mismo, incluso si solo recibe advertencias de "declaración implícita".
Jabberwocky
45

Uno de los buenos argumentos de alto nivel en contra de emitir el resultado de a mallocmenudo no se menciona, aunque, en mi opinión, es más importante que los problemas conocidos de nivel inferior (como truncar el puntero cuando falta la declaración).

Una buena práctica de programación es escribir código, que sea lo más independiente posible del tipo. Esto significa, en particular, que los nombres de tipos deben mencionarse en el código lo menos posible o mejor no mencionarlos en absoluto. Esto se aplica a conversiones (evite conversiones innecesarias), tipos como argumentos de sizeof(evite usar nombres de tipos en sizeof) y, en general, todas las demás referencias a nombres de tipos.

Los nombres de los tipos pertenecen a las declaraciones. En la medida de lo posible, los nombres de tipos deben restringirse a declaraciones y solo a declaraciones.

Desde este punto de vista, este fragmento de código es malo

int *p;
...
p = (int*) malloc(n * sizeof(int));

y esto es mucho mejor

int *p;
...
p = malloc(n * sizeof *p);

no simplemente porque "no arroja el resultado de malloc", sino porque es independiente del tipo (o agnosítico del tipo, si lo prefiere), porque se ajusta automáticamente a cualquier tipo con el que pse declare, sin requerir ninguna intervención de el usuario.

Hormiga
fuente
Fwiw, creo que esta es más o menos la misma razón que esta: stackoverflow.com/questions/953112/… pero se centró en la independencia de tipo en lugar del bricolaje. Por supuesto, el primero se sigue del segundo (o viceversa), por lo que al menos se menciona a veces . :)
relájese
5
@unwind probablemente te refieres a SECO en lugar de bricolaje
kratenko
18

Se supone que las funciones no prototipadas regresan int.

Entonces estás lanzando un inta un puntero. Si los punteros son más anchos que ints en su plataforma, este es un comportamiento de alto riesgo.

Además, por supuesto, algunas personas consideran que las advertencias son errores, es decir, el código debería compilarse sin ellas.

Personalmente, creo que el hecho de que no necesite lanzar void *a otro tipo de puntero es una característica de C, y considero que el código que sí está roto.

relajarse
fuente
14
Creo que el compilador sabe más sobre el lenguaje que yo, así que si me advierte sobre algo, presto atención.
György Andrasek
3
En muchos proyectos, el código C se compila como C ++ donde es necesario convertir el void*.
laalto
nit: " por defecto , se supone que las funciones no prototipadas regresan int". - ¿Quiere decir que es posible cambiar el tipo de retorno de funciones no prototipadas?
pmg
1
@laalto - Lo es, pero no debería serlo. C es C, no C ++, y debe compilarse con un compilador de C, no con un compilador de C ++. No hay excusa: GCC (uno de los mejores compiladores de C) se ejecuta en casi todas las plataformas imaginables (y también genera código altamente optimizado). ¿Qué razones podría tener para compilar C con un compilador de C ++, además de la pereza y los estándares sueltos?
Chris Lutz
3
Ejemplo de código que es posible que desee para compilar ya que tanto C y C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Ahora, por qué querrías llamar a malloc en una función estática en línea, realmente no lo sé, pero los encabezados que funcionan en ambos no son desconocidos.
Steve Jessop
11

Si hace esto al compilar en modo de 64 bits, su puntero devuelto se truncará a 32 bits.

EDITAR: Lo siento por ser demasiado breve. Aquí hay un fragmento de código de ejemplo para fines de discusión.

principal()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Suponga que el puntero del montón devuelto es algo más grande de lo que se puede representar en un int, digamos 0xAB00000000.

Si no se crea un prototipo de malloc para devolver un puntero, el valor int devuelto estará inicialmente en algún registro con todos los bits significativos establecidos. Ahora el compilador dice, "bien, ¿cómo convierto un int en un puntero". Eso será una extensión de signo o una extensión cero de los 32 bits de bajo orden que se le ha dicho que malloc "devuelve" omitiendo el prototipo. Dado que int está firmado, creo que la conversión será una extensión de signo, que en este caso convertirá el valor a cero. Con un valor de retorno de 0xABF0000000, obtendrá un puntero distinto de cero que también causará algo de diversión cuando intente eliminar la referencia.

Peeter Joot
fuente
1
¿Podría explicar en detalle cómo ocurriría esto?
Robert S. Barnes
5
Creo que Peeter Joot se estaba dando cuenta de que "de forma predeterminada, se supone que las funciones no prototipadas devuelven int" sin incluir stdlib.h, y sizeof (int) es de 32 bits mientras que sizeof (ptr) es 64.
Prueba
4

Una regla de software reutilizable:

En el caso de escribir una función en línea en la que se usó malloc (), para que sea reutilizable también para el código C ++, haga una conversión de tipos explícita (por ejemplo, (char *)); de lo contrario, el compilador se quejará.

Prueba
fuente
con suerte, con la (reciente) inclusión de optimizaciones de tiempo de enlace en gcc (ver gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), ya no será necesario declarar funciones en línea en archivos de encabezado
Christoph
tienes malas ideas. ¿Es consciente de lo que es portátil y multiplataforma entre diferentes compiladores / versiones / arquitecturas? ok, puede que no. entonces, ¿qué significa reutilizable?
Prueba el
2
al escribir C ++, malloc / free NO es la metodología correcta. Más bien use nuevo / eliminar. IE, no debería haber llamadas / nada / zero a malloc / free en el código C ++
user3629249
3
@ user3629249: Al escribir una función que tiene que ser utilizable desde el interior o bien el código C o código C ++, utilizando malloc/ freepara ambos es apta para ser mejor que tratar de utilizar mallocen C y newen C ++, especialmente si las estructuras de datos son compartidos entre C y C ++ código y existe la posibilidad de que un objeto se cree en código C y se publique en código C ++ o viceversa.
Supercat
3

Un puntero vacío en C se puede asignar a cualquier puntero sin una conversión explícita. El compilador dará una advertencia, pero se puede reutilizar en C ++ mediante la conversión de tipos malloc()al tipo correspondiente. Sin fundición de tipos también se puede usar en C , porque C no es una verificación de tipos estricta . Pero C ++ es estrictamente una verificación de tipos, por lo que es necesario escribir malloc()en C ++.

Yogeesh HT
fuente
¡Si usa malloc en C ++, es mejor que tenga una maldita buena razón! ; p
antred