memoria - vector dinamico en c




¿Hago el resultado de malloc? (18)

En esta pregunta , alguien sugirió en un comment que no debería emitir el resultado de malloc , es decir,

int *sieve = malloc(sizeof(int) * length);

más bien que:

int *sieve = (int *) malloc(sizeof(int) * length);

Por qué sería este el caso?


No, no lanzas el resultado de malloc().

En general, no lanzas desde o haciavoid * .

Una razón típica que se da para no hacerlo es que el hecho de #include <stdlib.h>no pasar inadvertido. Esto ya no es un problema por mucho tiempo, ya que C99 hizo que las declaraciones de funciones implícitas fueran ilegales, por lo que si su compilador cumple con al menos C99, recibirá un mensaje de diagnóstico.

Pero hay una razón mucho más poderosa para no introducir lanzamientos innecesarios de punteros:

En C, un puntero emitido es casi siempre un error . Esto se debe a la siguiente regla ( §6.5 p7 en N1570, el último borrador para C11):

Un objeto debe tener acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos:
- un tipo compatible con el tipo efectivo del objeto,
- una versión calificada de un tipo compatible con el tipo efectivo del objeto,
- un tipo que es el tipo firmado o sin signo que corresponde al tipo efectivo del objeto,
- un tipo que es el tipo firmado o sin signo que corresponde a una versión calificada del tipo efectivo del objeto,
- un tipo agregado o de unión que incluye uno de los tipos mencionados anteriormente entre sus miembros (incluido, recursivamente, un miembro de una subagregación o unión contenida), o
- un tipo de carácter.

Esto también se conoce como la regla de alias estricta . Entonces el siguiente código es un comportamiento indefinido :

long x = 5;
double *p = (double *)&x;
double y = *p;

Y, a veces sorprendentemente, lo siguiente es también:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

A veces, usted no necesita emitir punteros, pero teniendo en cuenta la regla de alias estricto , hay que tener mucho cuidado con él. Por lo tanto, cualquier aparición de un puntero fundido en su código es un lugar donde debe verificar su validez . Por lo tanto, nunca se escribe un puntero innecesario.

tl; dr

En pocas palabras: debido a que en C, cualquier aparición de un lanzamiento de puntero debe mostrar una bandera roja para el código que requiere atención especial, nunca debe escribir lanzamientos de puntero innecesarios .

Notas al margen:

  • Hay casos en los que realmente necesita una conversión void *, por ejemplo, si desea imprimir un puntero:

    int x = 5;
    printf("%p\n", (void *)&x);
    

    El reparto es necesario aquí, porque printf()es una función variable, por lo que las conversiones implícitas no funcionan.

  • En C ++, la situación es diferente. Los tipos de punteros de envío son algo comunes (y correctos) cuando se trata de objetos de clases derivadas. Por lo tanto, tiene sentido que en C ++, la conversión hacia y desde void *es no implícita. C ++ tiene un conjunto completo de diferentes sabores de fundición.


  1. Como se indicó en otros, no es necesario para C, sino para C ++.

  2. Incluir la conversión puede permitir que un programa o función de C se compile como C ++.

  3. En C no es necesario, ya que void * se promueve de forma automática y segura a cualquier otro tipo de puntero.

  4. Pero si realiza el lanzamiento, puede ocultar un error si olvidó incluir stdlib.h . Esto puede causar bloqueos (o, peor aún, no causar un choque hasta mucho más tarde en alguna parte totalmente diferente del código).

    Porque stdlib.h contiene el prototipo para malloc se encuentra. En ausencia de un prototipo para malloc, el estándar requiere que el compilador de C asuma que malloc devuelve un int. Si no hay conversión, se emite una advertencia cuando este entero se asigna al puntero; sin embargo, con el reparto, esta advertencia no se produce, ocultando un error.


Como se indicó en otros, no es necesario para C, sino para C ++. Si crees que vas a compilar tu código C con un compilador de C ++, por alguna razón, puedes usar una macro como:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

De esa manera aún puedes escribirlo de una manera muy compacta:

int *sieve = NEW(int, 1);

y se compilará para C y C ++.


De la Wikipedia

Ventajas del casting

  • Incluir la conversión puede permitir que un programa o función de C se compile como C ++.

  • El reparto permite versiones pre-1989 de malloc que originalmente devolvieron un char *.

  • El lanzamiento puede ayudar al desarrollador a identificar inconsistencias en el tamaño del tipo si el tipo de puntero de destino cambia, particularmente si el puntero se declara lejos de la llamada a malloc () (aunque los compiladores modernos y los analizadores estáticos pueden advertir sobre tal comportamiento sin requerir el lanzamiento).

Desventajas del casting

  • Bajo el estándar ANSI C, la conversión es redundante.

  • Agregar el modelo puede ocultar el error de incluir el encabezado stdlib.h , en el que se encuentra el prototipo de malloc. En ausencia de un prototipo para malloc, el estándar requiere que el compilador de C asuma que malloc devuelve un int. Si no hay conversión, se emite una advertencia cuando este entero se asigna al puntero; sin embargo, con el reparto, esta advertencia no se produce, ocultando un error. En ciertas arquitecturas y modelos de datos (como LP64 en sistemas de 64 bits, donde long y los punteros son de 64 bits y int de 32 bits), este error puede provocar un comportamiento indefinido, ya que el malloc declarado implícitamente devuelve un 32- valor de bit, mientras que la función realmente definida devuelve un valor de 64 bits. Dependiendo de las convenciones de llamada y el diseño de la memoria, esto puede resultar en aplastamiento de la pila. Es menos probable que este problema pase desapercibido en los compiladores modernos, ya que producen advertencias de manera uniforme de que se ha utilizado una función no declarada, por lo que aún aparecerá una advertencia. Por ejemplo, el comportamiento predeterminado de GCC es mostrar una advertencia que dice "declaración implícita incompatible de la función incorporada", independientemente de si la conversión está presente o no.

  • Si el tipo de puntero se cambia en su declaración, también se puede necesitar cambiar todas las líneas donde se llama y se lanza malloc.

Aunque malloc sin casting es el método preferido y la mayoría de los programadores experimentados lo eligen , debes usar el que quieras tener al tanto de los problemas.

es decir: si necesita compilar el programa en C como C ++ (aunque son un idioma diferente), debe usar malloc con el casting.


El tipo devuelto es void *, que se puede convertir al tipo deseado de puntero de datos para que no se pueda hacer referencia.


En C, no es necesario emitir el valor de retorno de malloc . El puntero para anular devuelto por malloc se convierte automáticamente al tipo correcto. Sin embargo, si desea que su código se compile con un compilador de C ++, se necesita una conversión. Una alternativa preferida entre la comunidad es usar lo siguiente:

int *sieve = malloc(sizeof *sieve * length);

lo que además le libera de tener que preocuparse por cambiar el lado derecho de la expresión si alguna vez cambia el tipo de sieve .

Los moldes son malos, como han señalado las personas. Especialmente los lanzamientos de puntero.


En C, puede convertir implícitamente un puntero nulo a cualquier otro tipo de puntero, por lo que no es necesario un lanzamiento. El uso de uno puede sugerir al observador casual que hay alguna razón por la que se necesita uno, lo cual puede ser engañoso.


En el lenguaje C, se puede asignar un puntero a cualquier puntero, por lo que no debe usar un tipo de conversión. Si desea una asignación "segura", puedo recomendar las siguientes funciones de macro, que siempre uso en mis proyectos de C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

Con estos en su lugar simplemente puede decir

NEW_ARRAY(sieve, length);

Para matrices no dinámicas, la tercera macro de la función must-have es

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

lo que hace que los bucles de matriz sean más seguros y convenientes:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

Las personas acostumbradas a GCC y Clang están echadas a perder. No es tan bueno por ahí.

A lo largo de los años he estado bastante horrorizado por los compiladores asombrosamente envejecidos que me han requerido usar. A menudo, las empresas y los gerentes adoptan un enfoque ultra conservador para cambiar compiladores y ni siquiera probarán si un nuevo compilador (con mejor cumplimiento de estándares y optimización de código) funcionará en su sistema. La realidad práctica para los desarrolladores que trabajan es que cuando estás codificando necesitas cubrir tus bases y, desafortunadamente, fundir mallocs es un buen hábito si no puedes controlar qué compilador se puede aplicar a tu código.

También sugeriría que muchas organizaciones apliquen un estándar de codificación propio y que ese sea ​​el método que las personas deben seguir si está definido. En ausencia de una guía explícita, tiendo a buscar la mayor probabilidad de compilar en todas partes, en lugar de una adhesión servil a un estándar.

El argumento de que no es necesario según los estándares actuales es bastante válido. Pero ese argumento omite los aspectos prácticos del mundo real. No codificamos en un mundo regido exclusivamente por el estándar del día, sino por los aspectos prácticos de lo que me gusta llamar "el campo de la realidad de la administración local". Y eso está doblado y torcido más de lo que lo fue el tiempo espacial. :-)

YMMV.

Tiendo a pensar en fundir malloc como una operación defensiva. No es bonita, no es perfecta, pero en general es segura. (Honestamente, si no has incluido stdlib.h, entonces has tenido más problemas que con malloc!).


Lo haces , porque

  • Hace que su código sea más portátil entre C y C ++, y como lo demuestra la experiencia de SO, muchos programadores afirman que están escribiendo en C cuando realmente están escribiendo en C ++ (o C más las extensiones del compilador local).
  • Si no lo hace, puede ocultar un error : tenga en cuenta todos los ejemplos de SO de confusión al escribir el type * contra el type ** .
  • La idea de que evita que se dé cuenta de que no se ha podido #include un archivo de encabezado adecuado pierde el bosque para los árboles . Es lo mismo que decir "no te preocupes por el hecho de que no hayas podido pedirle al compilador que se queje por no haber visto prototipos, ¡que molesta es la cosa REAL que debes recordar!"
  • Obliga a una verificación cruzada cognitiva extra . Pone el (supuesto) tipo deseado justo al lado de la aritmética que estás haciendo para el tamaño bruto de esa variable. Apuesto a que podrías hacer un estudio SO que muestre que los errores malloc() se capturan mucho más rápido cuando hay un reparto. Al igual que con las afirmaciones, las anotaciones que revelan la intención disminuyen los errores.
  • Repetirse de una manera que la máquina puede verificar es a menudo una gran idea. De hecho, eso es lo que es una afirmación, y este uso de cast es una afirmación. Las afirmaciones siguen siendo la técnica más general que tenemos para corregir el código, ya que Turing tuvo la idea hace muchos años.

No lanzas el resultado de malloc, porque al hacerlo agrega un desorden inútil a tu código.

La razón más común por la cual las personas emiten el resultado de malloc es porque no están seguras de cómo funciona el lenguaje C. Esa es una señal de advertencia: si no sabes cómo funciona un mecanismo de lenguaje en particular, no hagas una conjetura. Búscala o pregunta en .

Algunos comentarios:

  • Un puntero vacío puede convertirse a / desde cualquier otro tipo de puntero sin una conversión explícita (C11 6.3.2.3 y 6.5.16.1).

  • Sin embargo, C ++ no permitirá una conversión implícita entre void* y otro tipo de puntero. Así que en C ++, el reparto habría sido correcto. Pero si programa en C ++, debería usar new y no malloc (). Y nunca debes compilar código C usando un compilador de C ++.

    Si necesita admitir C y C ++ con el mismo código fuente, use los modificadores del compilador para marcar las diferencias. No intente poner ambos estándares de idioma con el mismo código, ya que no son compatibles.

  • Si un compilador de C no puede encontrar una función porque olvidó incluir el encabezado, obtendrá un error del compilador / vinculador al respecto. Entonces, si olvidó incluir <stdlib.h> eso no es problema, no podrá construir su programa.

  • En los compiladores antiguos que siguen una versión de la norma que tiene más de 25 años, olvidarse de incluir <stdlib.h> podría resultar en un comportamiento peligroso. Porque en ese antiguo estándar, las funciones sin un prototipo visible convertían implícitamente el tipo de retorno a int . Lanzar el resultado de malloc explícitamente ocultaría este error.

    Pero eso no es realmente un problema. No está utilizando una computadora de 25 años, ¿por qué usaría un compilador de 25 años?


Puse en el modelo simplemente para mostrar la desaprobación del agujero feo en el sistema de tipos, que permite que el código como el siguiente fragmento de código se compile sin diagnósticos, aunque no se utilicen modelos para provocar la conversión incorrecta:

double d;
void *p = &d;
int *q = p;

Ojalá no existiera (y no lo hace en C ++) y así lo lance. Representa mi gusto, y mi política de programación. No solo estoy lanzando un puntero, sino que efectivamente, emitiendo un voto y expulsando demonios de estupidez . Si realmente no puedo eliminar la estupidez , al menos déjame expresarte el deseo de hacerlo con un gesto de protesta.

De hecho, una buena práctica es envolver malloc (y amigos) con funciones que devuelven caracteres unsigned char * y, básicamente, nunca usar void * en su código. Si necesita un puntero genérico a cualquier objeto, use un char * o un unsigned char * , y haga lanzamientos en ambas direcciones. La única relajación que puede permitirse, tal vez, es usar funciones como memset y memcpy sin moldes.

Sobre el tema de la conversión y la compatibilidad con C ++, si escribe su código de manera que se compile como C y C ++ (en cuyo caso debe emitir el valor de retorno malloccuando se lo asigna a otra cosa void *), puede hacer una muy útil cosa para ti: puedes usar macros para la conversión que se traducen en conversiones de estilo C ++ al compilar como C ++, pero se reducen a una conversión C cuando se compila como C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

Si se adhiere a estas macros, entonces una simple grepbúsqueda en su base de código para estos identificadores le mostrará dónde están todos sus lanzamientos, para que pueda revisar si alguno de ellos es incorrecto.

Luego, en el futuro, si compila regularmente el código con C ++, impondrá el uso de una conversión apropiada. Por ejemplo, si usa strip_qualsolo para eliminar consto volatile, pero el programa cambia de tal manera que ahora implica una conversión de tipo, obtendrá un diagnóstico y tendrá que usar una combinación de conversiones para obtener la conversión deseada.

Para ayudarlo a adherirse a estas macros, el compilador GNU C ++ (¡no C!) Tiene una hermosa característica: un diagnóstico opcional que se produce para todas las ocurrencias de modelos de estilo C.

     -Wold-style-cast (C++ and Objective-C++ only)
         Warn if an old-style (C-style) cast to a non-void type is used
         within a C++ program.  The new-style casts (dynamic_cast,
         static_cast, reinterpret_cast, and const_cast) are less vulnerable
         to unintended effects and much easier to search for.

Si su código C se compila como C ++, puede usar esta -Wold-style-castopción para averiguar todas las apariciones de la (type)sintaxis de conversión que pueden introducirse en el código, y hacer un seguimiento de estos diagnósticos reemplazándolo con una opción adecuada de entre las macros anteriores combinación, si es necesario).

Este tratamiento de conversiones es la justificación técnica independiente más grande para trabajar en una "C limpia": el dialecto combinado C y C ++, que a su vez técnicamente justifica la conversión del valor de retorno de malloc.


Un puntero de vacío es un puntero genérico y C admite la conversión implícita de un tipo de puntero de vacío a otros tipos, por lo que no hay necesidad de encasillarlo explícitamente.

Sin embargo, si desea que el mismo código funcione perfectamente compatible en una plataforma C ++, que no es compatible con la conversión implícita, debe realizar el encasillado, por lo que todo depende de la facilidad de uso.


El casting de malloc es innecesario en C pero obligatorio en C ++.

El casting no es necesario en C debido a:

  • void * se promueve de forma automática y segura a cualquier otro tipo de puntero en el caso de C.
  • Puede ocultar un error si olvidó incluir <stdlib.h>. Esto puede causar choques.
  • Si los punteros y los enteros tienen un tamaño diferente, entonces está ocultando una advertencia al lanzar y podría perder bits de su dirección devuelta.
  • Si el tipo de puntero se cambia en su declaración, es posible que también deba cambiar todas las líneas donde mallocse llama y se lanza.

Por otro lado, el lanzamiento puede aumentar la portabilidad de su programa. es decir, permite que un programa o función de C compile como C ++.


La conversión es solo para C ++ no C. En el caso de que esté utilizando un compilador de C ++, es mejor que lo cambie a C compilador.


Lo mejor que se puede hacer al programar en C siempre que sea posible:

  1. Haga que su programa se compile a través de un compilador de C con todas las advertencias activadas -Wally corrija todos los errores y advertencias
  2. Asegúrese de que no hay variables declaradas como auto
  3. Luego compílelo usando un compilador de C ++ con -Wally -std=c++11. Solucionar todos los errores y advertencias.
  4. Ahora compila usando el compilador de C otra vez. Su programa ahora debería compilarse sin ninguna advertencia y contener menos errores.

Este procedimiento le permite aprovechar la comprobación estricta de tipos de C ++, lo que reduce el número de errores. En particular, este procedimiento lo obliga a incluir stdlib.ho obtendrá

malloc No fue declarado dentro de este ámbito.

y también te obliga a lanzar el resultado malloco obtendrás

conversión inválida de void*aT*

o lo que sea tu tipo de objetivo.

Los únicos beneficios de escribir en C en lugar de C ++ que puedo encontrar son

  1. C tiene un ABI bien especificado
  2. C ++ puede generar más código [excepciones, RTTI, plantillas, polimorfismo de tiempo de ejecución ]

Observe que, en el caso ideal, las segundas contras deberían desaparecer cuando se usa el subconjunto común a C junto con la característica polimórfica estática .

Para aquellos que encuentren inconvenientes en las reglas estrictas de C ++, podemos usar la función C ++ 11 con el tipo inferido

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

El concepto detrás del puntero de vacío es que se puede convertir a cualquier tipo de datos por lo que malloc devuelve vacío. También debe tener en cuenta el encasillado automático. Por lo tanto, no es obligatorio lanzar el puntero, aunque debe hacerlo. Ayuda a mantener el código limpio y ayuda a depurar


Prefiero hacer el reparto, pero no manualmente. Mi favorito es usar g_newy g_new0macros de glib. Si no se usa glib, agregaría macros similares. Esas macros reducen la duplicación de código sin comprometer la seguridad del tipo. Si el tipo es incorrecto, obtendrías una conversión implícita entre los punteros que no son nulos, lo que provocaría una advertencia (error en C ++). Si olvida incluir el encabezado que define g_newy g_new0, obtendrá un error. g_newy g_new0ambos toman los mismos argumentos, a diferencia de mallocque toma menos argumentos que calloc. Solo agregue 0para obtener memoria cero inicializada. El código puede compilarse con un compilador de C ++ sin cambios.





casting