Pregunta

Recibí un comentario a mi respuesta en este hilo:

¿Malloc dentro de una llamada de función parece liberarse al regresar?

En resumen, tenía un código como este:

int * somefunc (void)
{
  int * temp = (int*) malloc (sizeof (int));
  temp[0] = 0;
  return temp;
}

Recibí este comentario:

¿Puedo decir, por favor no lanzar el valor de retorno de Malloc?No es necesario y puede ocultar errores.

Estoy de acuerdo en que el elenco no es necesario en C.Es obligatorio en C++, por lo que normalmente los agrego en caso de que algún día tenga que portar el código a C++.

Sin embargo, me pregunto cómo este tipo de conversiones pueden ocultar errores.¿Algunas ideas?

Editar:

Parece que hay argumentos muy buenos y válidos de ambas partes.Gracias por publicar, amigos.

¿Fue útil?

Solución

Parece apropiado publicar una respuesta, ya que dejé el comentario :P

Básicamente, si olvidas incluir stdlib.h el compilador asumirá malloc devuelve un int.Sin lanzar, recibirás una advertencia.Con el casting no lo harás.

Entonces, al transmitir no obtienes nada y corres el riesgo de suprimir advertencias legítimas.

Se ha escrito mucho sobre esto; una búsqueda rápida en Google arrojará explicaciones más detalladas.

editar

Se ha argumentado que

TYPE * p;
p = (TYPE *)malloc(n*sizeof(TYPE));

lo hace obvio cuando accidentalmente no asignas suficiente memoria porque, digamos, pensaste p era TYPe no TYPE, y por lo tanto deberíamos lanzar malloc porque la ventaja de este método anula el menor costo de suprimir accidentalmente las advertencias del compilador.

Me gustaría señalar 2 cosas:

  1. Deberías escribir p = malloc(sizeof(*p)*n); para asegurarse siempre de asignar la cantidad correcta de espacio
  2. Con el enfoque anterior, debe realizar cambios en 3 lugares si alguna vez cambia el tipo de p:una vez en la declaración, una vez en la malloc, y una vez en el elenco.

En resumen, sigo creyendo personalmente que no hay necesidad de calcular el valor de retorno de malloc y ciertamente no es una buena práctica.

Otros consejos

Esta pregunta está etiquetada tanto para C como para C++, por lo que tiene al menos dos respuestas, en mi humilde opinión:

C

Ejem...Haz lo que quieras.

Creo que la razón dada anteriormente "Si no incluye" stdlib "no recibirá una advertencia" no es válida porque no se debe confiar en este tipo de trucos para no olvidarse de incluir un encabezado.

La verdadera razón que podría hacerte no escribir el reparto es que el compilador de C ya ha emitido silenciosamente un void * en cualquier tipo de puntero que desee, por lo que hacerlo usted mismo es excesivo e inútil.

Si desea tener seguridad de tipos, puede cambiar a C++ o escribir su propia función contenedora, como:

int * malloc_Int(size_t p_iSize) /* number of ints wanted */
{
   return malloc(sizeof(int) * p_iSize) ;
}

C++

A veces, incluso en C++, hay que aprovechar las utilidades malloc/realloc/free.Entonces tendrás que lanzar.Pero tu ya lo sabías.Usar static_cast<>() será mejor, como siempre, que el cast estilo C.

Y en C, puedes anular malloc (y realloc, etc.) a través de plantillas para lograr seguridad de tipos:

template <typename T>
T * myMalloc(const size_t p_iSize)
{
 return static_cast<T *>(malloc(sizeof(T) * p_iSize)) ;
}

Que se usaría como:

int * p = myMalloc<int>(25) ;
free(p) ;

MyStruct * p2 = myMalloc<MyStruct>(12) ;
free(p2) ;

y el siguiente código:

// error: cannot convert ‘int*’ to ‘short int*’ in initialization
short * p = myMalloc<int>(25) ;
free(p) ;

no se compilará, entonces, no hay problema.

Con todo, en C++ puro, ahora no tienes excusa si alguien encuentra más de un malloc C dentro de tu código...:-)

Cruce de C + C ++

A veces, desea producir código que se compile tanto en C como en C++ (por cualquier motivo...¿No es ese el objetivo de C++? extern "C" {} ¿bloquear?).En este caso, C++ exige la conversión, pero C no entenderá la palabra clave static_cast, por lo que la solución es la conversión estilo C (que todavía es legal en C++ exactamente por este tipo de razones).

Tenga en cuenta que incluso escribiendo código C puro, compilarlo con un compilador de C++ generará muchas más advertencias y errores (por ejemplo, intentar utilizar una función sin declararla primero no se compilará, a diferencia del error mencionado anteriormente).

Entonces, para estar seguro, escriba código que se compile limpiamente en C++, estudie y corrija las advertencias y luego use el compilador de C para producir el binario final.Esto significa, nuevamente, escribir el reparto, en un reparto estilo C.

Un posible error que puede introducir es si está compilando en un sistema de 64 bits usando C (no C++).

Básicamente, si olvidas incluir stdlib.h, se aplicará la regla int predeterminada.Por lo tanto, el compilador asumirá felizmente que malloc tiene el prototipo de int malloc(); En muchos sistemas de 64 bits, un int es de 32 bits y un puntero es de 64 bits.

¡Oh, oh, el valor se trunca y solo obtienes los 32 bits inferiores del puntero!Ahora, si lanzas el valor de retorno de malloc, este error está oculto por el elenco.Pero si no lo hace, obtendrá un error (algo parecido a "no se puede convertir int a T *").

Por supuesto, esto no se aplica a C++ por dos razones.En primer lugar, no tiene una regla int predeterminada y, en segundo lugar, requiere la conversión.

Sin embargo, en general, deberías ser nuevo en el código C++ de todos modos :-P.

Bueno, creo que es exactamente lo contrario: siempre transfiérelo directamente al tipo necesario. ¡Sigue leyendo aquí!

El argumento "olvidé stdlib.h" es un hombre de paja.Los compiladores modernos detectarán y advertirán del problema (gcc -Wall).

Siempre debes emitir el resultado de malloc inmediatamente.No hacerlo debería considerarse un error, y no sólo porque fallará como C++.Si está apuntando a una arquitectura de máquina con diferentes tipos de punteros, por ejemplo, podría terminar con un error muy complicado si no coloca el elenco.

Editar:El comentarista Evan Terán es correcto.Mi error fue pensar que el compilador no tenía que trabajar en un puntero vacío en ningún contexto.Me asusto cuando pienso en errores de puntero FAR, por lo que mi intuición es lanzar todo.Gracias Evan!

En realidad, la única forma en que una conversión podría ocultar un error es si estuviera convirtiendo de un tipo de datos a un tipo de datos más pequeño y perdiera datos, o si estuviera convirtiendo peras en manzanas.Tomemos el siguiente ejemplo:

int int_array[10];
/* initialize array */
int *p = &(int_array[3]);
short *sp = (short *)p;
short my_val = *sp;

en este caso, la conversión a short eliminaría algunos datos del int.Y luego este caso:

struct {
    /* something */
} my_struct[100];

int my_int_array[100];
/* initialize array */
struct my_struct *p = &(my_int_array[99]);

en el que terminarías apuntando al tipo de datos incorrecto, o incluso a una memoria no válida.

Pero en general, y si sabes lo que haces, está bien hacer el casting.Más aún cuando obtienes memoria de malloc, que devuelve un puntero vacío que no puedes usar en absoluto a menos que lo conviertas, y la mayoría de los compiladores te avisarán si estás convirtiendo algo como el valor l (el valor del lado izquierdo de la tarea) no puedo tomar de todos modos.

#if CPLUSPLUS
#define MALLOC_CAST(T) (T)
#else
#define MALLOC_CAST(T)
#endif
...
int * p;
p = MALLOC_CAST(int *) malloc(sizeof(int) * n);

o, alternativamente

#if CPLUSPLUS
#define MYMALLOC(T, N) static_cast<T*>(malloc(sizeof(T) * N))
#else
#define MYMALLOC(T, N) malloc(sizeof(T) * N)
#endif
...
int * p;
p = MYMALLOC(int, n);

La gente ya ha citado las razones que suelo sacar a relucir:el viejo argumento (ya no aplicable a la mayoría de los compiladores) sobre no incluir stdlib.h y usando sizeof *p para asegurarse de que los tipos y tamaños siempre coincidan independientemente de las actualizaciones posteriores.Quiero señalar otro argumento en contra del casting.Es pequeño, pero creo que se aplica.

C tiene un tipo bastante débil.La mayoría de las conversiones de tipos seguras ocurren automáticamente y la mayoría de las inseguras requieren una conversión.Considerar:

int from_f(float f)
{
    return *(int *)&f;
}

Ese es un código peligroso.Es un comportamiento técnicamente indefinido, aunque en la práctica hará lo mismo en casi todas las plataformas en las que lo ejecute.Y el elenco ayuda a decirte "Este código es un truco terrible".

Considerar:

int *p = (int *)malloc(sizeof(int) * 10);

Veo un yeso y me pregunto: "¿Por qué es necesario?¿Dónde está el truco?" Se me erizan los pelos de la nuca porque algo malo está sucediendo, cuando en realidad el código es completamente inofensivo.

Mientras usemos C, los elenco (especialmente los castores de puntero) son una forma de decir "hay algo malvado y fácilmente rompible aquí". Pueden lograr lo que necesita lograr, pero le indican a usted y a los futuros mantenedores que los niños no están bien.

Usar yesos en cada malloc disminuye la indicación de "hack" del lanzamiento del puntero.Hace que sea menos discordante ver cosas como *(int *)&f;.

Nota:C y C++ son lenguajes diferentes.C tiene un tipo débil, C++ tiene un tipo más fuerte.los moldes son necesarios en C++, aunque no indican un hack en absoluto, debido (en mi humilde opinión) al sistema de tipos C++ innecesariamente fuerte.(Realmente, este caso particular es el único lugar donde creo que el sistema de tipos C++ es "demasiado fuerte", pero no puedo pensar en ningún lugar donde sea "demasiado débil", lo que lo hace en general demasiado fuerte para mi gusto).

Si le preocupa la compatibilidad con C++, no lo haga.Si estás escribiendo C, usa un compilador de C.Hay muchos realmente buenos disponibles para cada plataforma.Si por alguna razón tonta tener Para escribir código C que se compila limpiamente como C++, en realidad no estás escribiendo C.Si necesita portar C a C++, debería realizar muchos cambios para que su código C sea más idiomático C++.

Si no puedes hacer nada de eso, tu código no será bonito sin importar lo que hagas, por lo que realmente no importa cómo decidas transmitirlo en ese momento.Me gusta la idea de usar plantillas para crear un nuevo asignador que devuelva el tipo correcto, aunque eso es básicamente reinventar el new palabra clave.

Convertir una función que devuelve (void *) para que sea (int *) es inofensivo:estás lanzando un tipo de puntero a otro.

Lo más probable es que sea incorrecto convertir una función que devuelve un número entero para que sea un puntero.El compilador lo habría marcado si no lo hubieras emitido explícitamente.

Un posible error podría (dependiendo de esto, si es lo que realmente desea o no) ser mallocar con una escala de tamaño y asignarlo a un puntero de un tipo diferente.P.ej.,

int *temp = (int *)malloc(sizeof(double));

Puede haber casos en los que quieras hacer esto, pero sospecho que son raros.

Creo que deberías ponerte el yeso.Considere que existen tres ubicaciones para los tipos:

T1 *p;
p = (T2*) malloc(sizeof(T3));

Las dos líneas de código pueden estar muy separadas.Por lo tanto, es bueno que el compilador hacer cumplir que T1 == T2.Es más fácil verificar visualmente que T2 == T3.

Si te pierdes el elenco T2, entonces debes esperar que T1 == T3.

Por otro lado, falta el argumento stdlib.h, pero creo que es menos probable que sea un problema.

Por otro lado, si alguna vez necesita portar el código a C++, es mucho mejor utilizar el operador 'nuevo'.

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