¿Lanzo el resultado de malloc?

2408

En esta pregunta , alguien sugirió en un comentario que yo no emitir el resultado 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?

Patrick McDonald
fuente
222
Además, es más fácil de escribir sieve = malloc (sizeof * sieve * length);
William Pursell
3
Las respuestas aquí son un campo minado de unilateralidad. La respuesta es, depende". No es necesario hacer que el programa funcione. Sin embargo, algunos estándares de codificación lo requieren ... Por ejemplo, vea CERT C Coding Standard
Dr. Person Person II
Curiosamente, todos están de acuerdo en que no emites NULL. (probablemente por eso se introdujo C ++ nullptr: C ++ no permite ninguna conversión de puntero implícita)
Sapphire_Brick
Puedes, pero no es necesario. Pero necesitas hacerlo en C ++.
user12211554

Respuestas:

2217

No ; usted no emitir el resultado, ya que:

  • Es innecesario, ya que void *se promociona de forma automática y segura a cualquier otro tipo de puntero en este caso.
  • Agrega desorden al código, las conversiones no son muy fáciles de leer (especialmente si el tipo de puntero es largo).
  • Te hace repetir, lo que generalmente es malo.
  • Puede ocultar un error si olvidó incluirlo <stdlib.h>. Esto puede causar bloqueos (o, lo que es peor, no causar un bloqueo hasta más tarde en una parte totalmente diferente del código). Considere lo que sucede si los punteros y los enteros tienen tamaños diferentes; entonces está ocultando una advertencia al emitir y puede perder partes de su dirección devuelta. Nota: a partir de C99, las funciones implícitas han desaparecido de C, y este punto ya no es relevante ya que no existe un supuesto automático de que las funciones no declaradas vuelven int.

Como aclaración, tenga en cuenta que dije "usted no lanza", no "no necesita lanzar". En mi opinión, es un fracaso incluir el elenco, incluso si lo hiciste bien. Simplemente no hay beneficios al hacerlo, pero hay un montón de riesgos potenciales, e incluir el elenco indica que no conoce los riesgos.

También tenga en cuenta, como señalan los comentaristas, que lo anterior habla de C directo, no de C ++. Creo firmemente en C y C ++ como lenguajes separados.

Para agregar más, su código repite innecesariamente la información de tipo ( int) que puede causar errores. Es mejor desreferenciar el puntero que se utiliza para almacenar el valor de retorno, para "bloquear" los dos juntos:

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

Esto también mueve lengthal frente para una mayor visibilidad y elimina los paréntesis redundantes con sizeof; que sólo son necesarios cuando el argumento es un nombre de tipo. Muchas personas parecen no saber (o ignorar) esto, lo que hace que su código sea más detallado. Recuerde: ¡ sizeofno es una función! :)


Si bien moverse lengthhacia el frente puede aumentar la visibilidad en algunos casos raros, también se debe prestar atención a que, en el caso general, debería ser mejor escribir la expresión como:

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

Dado que mantener el sizeofprimero, en este caso, asegura que la multiplicación se realice con al menos size_tmatemáticas.

Comparar: malloc(sizeof *sieve * length * width)vs. malloc(length * width * sizeof *sieve)el segundo puede desbordar el length * widthcuándo widthy lengthson tipos más pequeños que size_t.

relajarse
fuente
21
Por favor considere actualizar la respuesta. El lanzamiento ya no es peligroso, y repetir uno mismo no es necesariamente algo malo (la redundancia puede ayudar a detectar errores).
n. 'pronombres' m.
11
Los compiladores han cambiado. Un compilador actualizado le advertirá sobre la falta de una declaración de malloc.
n. 'pronombres' m.
55
@nm Ok. Creo que es malo asumir que cualquiera que lea aquí tiene un compilador particular. Además, como C11 desapareció todo el concepto de "función implícita", no lo sabía. Aún así, no veo el punto de agregar un elenco sin sentido. ¿También haces int x = (int) 12;solo para aclarar las cosas?
Descanse
22
@nm si lanzar explícitamente un puntero de vacío "ayudó" a resolver un error, lo más probable es que haya encontrado un comportamiento indefinido, lo que significaría que el programa en cuestión probablemente tenga un error mucho peor e desconocido que aún no ha encontrado. Y un día, en una fría noche de invierno, volverás a casa del trabajo para encontrar tu página de GitHub inundada de informes de problemas quejándose de demonios que vuelan por las narices de los usuarios
Braden Best
12
@unwind Incluso estoy de acuerdo contigo, (int)12no es comparable. 12 es un int, el elenco simplemente no hace nada. La retirada de malloc()es void *, no el tipo de puntero al que se ha lanzado. (Si no es void *así, la analogía (int)12sería (void*)malloc(…)lo que nadie está discutiendo).
Amin Negm-Awad
376

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

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

lo que además te libera de tener que preocuparte por cambiar el lado derecho de la expresión si alguna vez cambias el tipo de sieve.

Los moldes son malos, como la gente ha señalado. Especialmente moldes de puntero.

Dirkgently
fuente
71
@MAKZ Yo diría que malloc(length * sizeof *sieve)parece que sizeofes una variable, así que creo que malloc(length * sizeof(*sieve))es más legible.
Michael Anderson
21
Y malloc(length * (sizeof *sieve))aún más legible. EN MI HUMILDE OPINIÓN.
Toby Speight
19
Dejando a ()un lado el problema de @Michael Anderson , tenga en cuenta que su estilo sugerido cambió el orden. Tenga en cuenta cuándo se calcula el recuento de elementos length*width, mantener el sizeofprimero en este caso asegura que la multiplicación se realice con al menos size_tmatemáticas. Comparar malloc(sizeof( *ptr) * length * width)vs. malloc(length * width * sizeof (*ptr))- el segundo puede desbordar el length*widthcuando width,lengthhay tipos más pequeños que size_t.
chux
3
@chux no es obvio, pero la respuesta ha sido editada para que mi comentario sea menos pertinente - la sugerencia original fuemalloc(sizeof *sieve * length)
Michael Anderson
12
C no es C ++. Pretender que lo son en última instancia conducirá a la confusión y la tristeza. Si está usando C ++, entonces un reparto de estilo C también es malo (a menos que esté usando un compilador de C ++ muy antiguo). Y static_cast>()(o reinterpret_cast<>()) no es compatible con ningún dialecto de C.
David C.
349

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 extensiones de compilador local).
  • No hacerlo puede ocultar un error : tenga en cuenta todos los ejemplos SO de confusión sobre cuándo escribir type *versustype ** .
  • La idea de que te impide darte cuenta de que fallaste en #includeun archivo de encabezado apropiado pierde el bosque para los árboles . Es lo mismo que decir "no te preocupes por el hecho de que no pudiste pedirle al compilador que se quejara por no ver prototipos, ¡ese molesto stdlib.h es lo REAL realmente importante para recordar!"
  • Fuerza una verificación cruzada cognitiva adicional . Pone el (deseado) tipo deseado justo al lado de la aritmética que está haciendo para el tamaño bruto de esa variable. Apuesto a que podrías hacer un estudio SO que muestre que los malloc()errores se detectan mucho más rápido cuando hay un elenco. Al igual que con las afirmaciones, las anotaciones que revelan la intención disminuyen los errores.
  • Repetirse de una manera que la máquina pueda verificar es a menudo una gran idea. De hecho, eso 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 obtener el código correcto, ya que a Turing se le ocurrió la idea hace muchos años.
Ron Burk
fuente
39
@ulidtko En caso de que no lo supiera, es posible escribir código que compila tanto como C como C ++. De hecho, la mayoría de los archivos de encabezado son así, y a menudo contienen código (macros y funciones en línea). Tener un archivo .c/ .cpppara compilar como ambos no es útil muy a menudo, pero un caso es agregar throwcompatibilidad con C ++ cuando se compila con el compilador de C ++ (pero return -1;cuando se compila con el compilador de C, o lo que sea).
hyde
37
Si alguien tuviera llamadas malloc en línea en un encabezado, no me impresionaría, #ifdef __cplusplus y la "C" {} externa son para este trabajo, sin agregar moldes adicionales.
Paul
15
Bueno, el punto 1 es irrelevante, ya que C! = C ++, los otros puntos también son triviales, si usa la variable en su mallocllamada: char **foo = malloc(3*sizeof(*foo));si es bastante completa: 3 punteros a punteros de caracteres. luego bucle y hacer foo[i] = calloc(101, sizeof(*(foo[i])));. Asigne una matriz de 101 caracteres, perfectamente inicializados a ceros. No se necesita yeso. cambie la declaración a unsigned charcualquier otro tipo, para el caso, y todavía está bien
Elias Van Ootegem
34
Cuando pensé que lo tenía, ¡ahí viene! Fantástica respuesta. ¡Es la primera vez aquí en StackOverflow que hago +1 dos respuestas opuestas! +1 No, no lanzas, y +1 ¡Sí, lanzas! Jajaja Ustedes son geniales. Y para mí y mis alumnos, me decidí: hago el reparto. El tipo de errores que cometen los estudiantes se detectan más fácilmente al lanzar.
Dr. Beco
15
@Leushenko: repetirlo de una manera que no puede ser validado por la máquina ni por la inspección local es malo. Repetirse de maneras que puedan ser validadas por tales medios es menos malo. Dado struct Zebra *p; ... p=malloc(sizeof struct Zebra);, el malloc no puede evitar duplicar información sobre el tipo de p, pero ni el compilador ni la inspección del código local detectarían ningún problema si un tipo cambiara pero el otro no. Cambie el código a p=(struct Zebra*)malloc(sizeof struct Zebra);y el compilador emitirá un chillido si el tipo de lanzamiento no coincide p, y la inspección local revelará ...
supercat
170

Como otros han dicho, no es necesario para C, pero sí para C ++. Si cree que va a compilar su código C con un compilador de C ++, por cualquier motivo, puede 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 puede escribirlo de una manera muy compacta:

int *sieve = NEW(int, 1);

y compilará para C y C ++.

quinmars
fuente
17
Como estás usando una macro de todos modos, ¿por qué no la usas newen la definición de C ++?
Hosam Aly
63
Porque no hay razón para hacerlo. Es principalmente para programas en C que se compilan con un compilador de C ++. Si vas a usar 'nuevo', lo único que obtienes son problemas. Entonces necesitas también una macro gratis. Y necesita una macro para liberar una matriz, una diferenciación que no existe en C.
quinmars
8
Sin mencionar si no eres tú quien libera la memoria, sino quizás una biblioteca C que estás utilizando, etc. Muchos posibles problemas sin ganancia.
quinmars
86
@Hosam: Sí, definitivamente lo es. Si usa newdebe usar deletey si usa malloc()debe hacerlo free(). Nunca los mezcles.
Graeme Perrow
17
Si uno va a adoptar este enfoque, llamar a la macro NEWes probablemente una mala idea ya que el recurso nunca se devuelve usando delete(o DELETE), por lo que está mezclando su vocabulario. En cambio, nombrarlo MALLOC, o más bien CALLOCen este caso, tendría más sentido.
mah
139

De la Wikipedia :

Ventajas del casting

  • La inclusión del elenco puede permitir que un programa o función C se compile como C ++.

  • El elenco permite versiones anteriores a 1989 de malloc que originalmente devolvieron un char *.

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

Desventajas de lanzar

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

  • Agregar el reparto puede enmascarar la falla al 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 suponga 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 punteros son de 64 bits e int es de 32 bits), este error puede dar lugar a 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 llamadas y el diseño de la memoria, esto puede provocar la destrucción de la pila. Es menos probable que este problema pase desapercibido en los compiladores modernos, Como uniformemente producen advertencias de que se ha utilizado una función no declarada, aparecerá una advertencia. Por ejemplo, el comportamiento predeterminado de GCC es mostrar una advertencia que dice "declaración implícita incompatible de función incorporada" independientemente de si el elenco está presente o no.

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

Aunque malloc sin casting es el método preferido y los programadores más experimentados lo eligen , debe usar el que desee para conocer los problemas.

es decir: si necesita compilar el programa C como C ++ (aunque es un lenguaje separado), debe emitir el resultado del uso malloc.

ashiquzzaman33
fuente
1
¿Qué significa " Casting 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 malloc()llamada "? ¿Podrías dar un ejemplo?
Spikatrix
3
@CoolGuy: Vea un comentario anterior sobre otra respuesta . Pero tenga en cuenta que el p = malloc(sizeof(*p) * count)idioma recoge los cambios en el tipo automáticamente, por lo que no tiene que recibir advertencias e ir a cambiar nada. Así que esta no es una ventaja real frente a la mejor alternativa para no lanzar.
Peter Cordes
77
Esta es la respuesta correcta: hay pros y contras, y se reduce a una cuestión de gustos (a menos que el código deba compilarse como C ++, entonces el reparto es obligatorio).
Peter - Restablece a Mónica el
3
El punto 3 es discutible, ya que si el tipo de puntero se cambia en su declaración, uno debe verificar cada instancia de malloc, realloc y la resolución libre de ese tipo. El casting te obligará a hacer exactamente eso.
Michaël Roy
104

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

PaulJWilliams
fuente
100

No emites el resultado de malloc, porque hacerlo agrega 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 seguros 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, entonces no adivines. Búscalo o pregunta en Stack Overflow.

Algunos comentarios:

  • Un puntero nulo se puede convertir 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. Entonces, en C ++, el reparto habría sido correcto. Pero si programa en C ++, debería usarnew y no malloc (). Y nunca debe compilar código C usando un compilador de C ++.

    Si necesita admitir C y C ++ con el mismo código fuente, use modificadores de compilación para marcar las diferencias. No intente saciar 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 de compilador / enlazador al respecto. Entonces, si olvidó incluir <stdlib.h>eso no es gran cosa, no podrá construir su programa.

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

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

Lundin
fuente
99
"desorden sin sentido" es una hipérbole desdeñosa que tiende a descarrilar cualquier posibilidad de convencer a cualquiera que no esté de acuerdo con usted. Un elenco ciertamente no tiene sentido; Las respuestas de Ron Burk y Kaz hacen argumentos a favor del casting con el que estoy muy de acuerdo. Si esas preocupaciones pesan más que las preocupaciones que menciona es una pregunta razonable. Para mí, sus preocupaciones parecen relativamente menores en comparación con las de ellos.
Don Hatch
"6.3.2.3 no admite un puntero vacío a / desde cualquier otro tipo de puntero sin una conversión explícita". ¿Quizás está pensando en "puntero a cualquier tipo de objeto"? "puntero vacío" y "puntero a una función" no son tan fácilmente convertibles.
chux
De hecho, la referencia estaba incompleta. La parte relevante para la "implicidad" es la regla de la asignación simple 6.5.16.1. "un operando es un puntero a un tipo de objeto, y el otro es un puntero a una versión calificada o no calificada de void". He agregado esta referencia a la respuesta para completar.
Lundin
91

En C obtienes una conversión implícita de void *cualquier otro puntero (de datos).

EFraim
fuente
66
@Jens: OK, quizás la redacción más adecuada es "conversión implícita". Como el uso de la variable integral en la expresión de coma flotante.
EFraim
@EFraim Eso realmente resultaría en un reparto, y uno implícito en eso.
Físico loco
71

Transmitir el valor devuelto por malloc()Ahora no es necesario , pero me gustaría agregar un punto que parece que nadie ha señalado:

En la antigüedad, es decir, antes de que ANSI C proporcione el void *tipo genérico de punteros, char *es el tipo para dicho uso. En ese caso, el elenco puede cerrar las advertencias del compilador.

Referencia: C Preguntas frecuentes

Yu Hao
fuente
2
Cerrar las advertencias del compilador es una mala idea.
Albert van der Horst
8
@AlbertvanderHorst No si lo estás haciendo resolviendo el problema exacto del que está la advertencia para advertirte.
Dan Bechard el
@Dan. Si al resolver el problema exacto se entiende una reescritura de una subrutina para devolver los tipos ANSI C modernos en lugar de char *, estoy de acuerdo. No llamaría a eso callar el compilador. No ceda ante los gerentes que insisten en que no hay advertencias del compilador, en lugar de usarlos en cada recopilación para encontrar posibles problemas. Groetjes Albert
Albert van der Horst
53

Solo agregando mi experiencia, estudiando ingeniería informática, veo que los dos o tres profesores que he visto escribir en C siempre emiten malloc, sin embargo, el que le pregunté (con un inmenso CV y ​​comprensión de C) me dijo que es absolutamente innecesario, pero solía ser absolutamente específico y hacer que los estudiantes tuvieran la mentalidad de ser absolutamente específicos. Esencialmente, el lanzamiento no cambiará nada en su funcionamiento, hace exactamente lo que dice, asigna memoria y el lanzamiento no lo afecta, obtienes la misma memoria e incluso si lo lanzas a otra cosa por error (y de alguna manera evades el compilador errores) C accederá de la misma manera.

Editar: El casting tiene un cierto punto. Cuando utiliza la notación de matriz, el código generado tiene que saber cuántos lugares de memoria tiene que avanzar para alcanzar el comienzo del siguiente elemento, esto se logra mediante la conversión. De esta manera, usted sabe que para un doble va 8 bytes adelante, mientras que para un int va 4, y así sucesivamente. Por lo tanto, no tiene ningún efecto si utiliza la notación de puntero, en notación de matriz se hace necesario.

usuario3079666
fuente
3
Excepto como ya se mencionó, el elenco podría ocultar errores y hacer que el código sea más difícil de analizar para el compilador o el analizador estático.
Lundin
2
"Básicamente, el casting no cambiará nada en cómo funciona". La conversión al tipo de coincidencia no debería cambiar nada, pero si el tipo de var cambia y la conversión ya no coincide, ¿podrían surgir problemas? IWO, el tipo de conversión y var deben mantenerse sincronizados: el doble del trabajo de mantenimiento.
chux - Restablece a Mónica el
Puedo ver por qué los profesores prefieren el casting. La transmisión puede ser útil desde un punto de vista educativo donde transmite información al instructor y no es necesario mantener el código del estudiante: su código desechable. Sin embargo, desde una perspectiva de codificación, revisión por pares y mantenimiento , p = malloc(sizeof *p * n);es tan simple y mejor.
chux - Restablecer Monica
53

No es obligatorio emitir los resultados de malloc, ya que regresa void*, y void*puede apuntar a cualquier tipo de datos.

usuario968000
fuente
34

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

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

Esfuerzo
fuente
2
No es un caso de uso normal compilar una sola fuente como C y C ++ (en oposición, por ejemplo, al uso de un archivo de encabezado que contiene declaraciones para vincular los códigos C y C ++). Usar mallocy amigos en C ++ es una buena señal de advertencia de que merece atención especial (o reescribir en C).
Toby Speight
1
"Un puntero vacío es un puntero genérico" -> "Un puntero vacío es un puntero de objeto genérico ". Los tamaños de los punteros de función pueden exceder void *, por lo tanto, void *es insuficiente para almacenar bien un puntero de función.
chux - Restablece a Mónica el
mi intención de esa línea era la misma, pero de todos modos gracias @chux por su sugerencia.
Endeavour
33

Esto es lo que dice el manual de referencia de la biblioteca GNU C :

Puede almacenar el resultado mallocen cualquier variable de puntero sin conversión, porque ISO C convierte automáticamente el tipo void *a otro tipo de puntero cuando sea necesario. Pero la conversión es necesaria en contextos distintos de los operadores de asignación o si desea que su código se ejecute en C. tradicional

Y, de hecho, el estándar ISO C11 (p347) lo dice así:

El puntero que se devuelve si la asignación se realiza correctamente se alinea adecuadamente para que pueda asignarse a un puntero a cualquier tipo de objeto con un requisito fundamental de alineación y luego usarse para acceder a dicho objeto o a una matriz de dichos objetos en el espacio asignado (hasta que el espacio está explícitamente desasignado)

Slothworks
fuente
31

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

permutas
fuente
1
void* se puede convertir al tipo deseado, pero no es necesario hacerlo, ya que se convertirá automáticamente. Por lo tanto, el reparto no es necesario y, de hecho, no es deseable por las razones mencionadas en las respuestas de alta puntuación.
Toby Speight
pero solo si necesita desreferenciarla "sobre la marcha", si crea una variable en su lugar, se convertirá de forma segura y automática en el tipo efectivo de la variable, sin conversión (en C).
Ferrarezi
28

En el lenguaje C, se puede asignar un puntero nulo a cualquier puntero, por lo que no debe usar un tipo de conversión. Si desea una asignación de "tipo seguro", puedo recomendar las siguientes funciones de macro, que siempre uso en mis proyectos en 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 función imprescindible 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++) {
   ...
}
August Karlstrom
fuente
"se puede asignar un puntero vacío a cualquier puntero de objeto " Los punteros de función son otro problema, aunque no malloc()uno.
chux
Asignar un void*puntero a / desde una función puede perder información, por lo que "un puntero vacío se puede asignar a cualquier puntero" es un problema en esos casos. Sin embargo void*, asignar un puntero desde, malloc() a cualquier objeto no es un problema.
chux - Restablece a Monica el
El docomentario del bucle se relaciona con macros que involucran un bucle pero que se está alejando de la pregunta del título. Eliminando ese comentario. Tomará este abajo más tarde también.
chux - Restablecer Monica
27

Depende del lenguaje de programación y el compilador. Si lo usa mallocen C, no es necesario escribir cast, ya que escribirá automáticamente cast. Sin embargo, si está utilizando C ++, debe escribir cast porque mallocdevolverá un void*tipo.

Jeyamaran
fuente
1
La función malloc también devuelve un puntero vacío en C, pero las reglas del lenguaje son diferentes de C ++.
August Karlstrom
16

Las personas acostumbradas a GCC y Clang están en mal estado. No es tan bueno por ahí.

A lo largo de los años, he estado bastante horrorizado por los compiladores asombrosamente viejos que me han requerido usar. A menudo, las empresas y los gerentes adoptan un enfoque ultraconservador para cambiar los compiladores y ni siquiera lo harán prueban 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, lanzar mallocs es un buen hábito si no puedes controlar qué compilador puede aplicarse a tu código.

También sugeriría que muchas organizaciones apliquen un estándar de codificación propio y que debería ser el método que la gente siga si se define. En ausencia de una guía explícita, tiendo a ir con mayor probabilidad de compilar en todas partes, en lugar de una servil adhesión 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 gestión local". Y eso está doblado y retorcido más de lo que nunca estuvo el tiempo espacial. :-)

YMMV.

Tiendo a pensar en lanzar Malloc como una operación defensiva. No es bonito, no es perfecto, pero generalmente es seguro. (En serio, si no ha incluido stdlib.h entonces tienes manera más problemas que la fundición malloc!).

StephenG
fuente
15

Puse el elenco simplemente para mostrar la desaprobación del agujero feo en el sistema de tipos, que permite que el código como el siguiente fragmento se compile sin diagnóstico, a pesar de que no se utilizan moldes para provocar la conversión incorrecta:

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

Desearía que no existiera (y no existe en C ++) y, por lo tanto, lancé. Representa mi gusto y mi política de programación. No solo estoy lanzando un puntero, sino efectivamente, votando y expulsando demonios de estupidez . Si no puedo realmente echar fuera la estupidez , a continuación, al menos permítanme expresar el deseo de hacerlo con un gesto de protesta.

De hecho, una buena práctica es envolver malloc(y amigos) con funciones que regresan unsigned char *, y básicamente nunca usarlas void *en su código. Si necesita un puntero genérico a cualquier objeto, use un char *o unsigned char *, y tenga conversiones en ambas direcciones. La única relajación que puede permitirse, tal vez, es usar funciones como memsety memcpysin yesos.

Sobre el tema de la compatibilidad de conversión y C ++, si escribe su código para que se compile como C y C ++ (en cuyo caso debe emitir el valor de retorno mallocal asignarlo a otra cosa que no sea void *), puede hacer algo muy útil cosa para usted: puede usar macros para la conversión que se traducen en conversiones de estilo C ++ cuando se compila 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, una simple grepbúsqueda en su base de código para estos identificadores le mostrará dónde están todos sus moldes, 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 un reparto apropiado. Por ejemplo, si usa strip_qualsolo para eliminar a consto volatile, pero el programa cambia de tal manera que ahora se trata de 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 apariciones de modelos de estilo C.

     -Wold-style-cast (solo C ++ y Objective-C ++)
         Avisar si se utiliza una conversión de estilo antiguo (estilo C) a un tipo no vacío
         dentro de un programa C ++. Los lanzamientos de estilo nuevo (dynamic_cast,
         static_cast, reinterpret_cast y const_cast) son menos vulnerables
         a efectos no deseados y mucho más fácil de buscar.

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 aparecer en el código, y hacer un seguimiento de estos diagnósticos reemplazándolo con una elección adecuada entre las macros anteriores (o un 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 de C y C ++, que a su vez técnicamente justifica emitir el valor de retorno de malloc.

Kaz
fuente
Como otro señaló, generalmente recomendaría no mezclar el código C y C ++. Sin embargo, si tiene buenas razones para hacerlo, las macros pueden ser útiles.
Phil1970
@ Phil1970 Todo está escrito en un dialecto cohesivo, que resulta ser portátil para los compiladores C y C ++, y aprovecha algunas capacidades de C ++. Debe estar todo compilado como C ++, o bien todo compilado como C.
Kaz
Es decir, lo que estaba tratando de decir en el comentario anterior es que no hay mezcla de C y C ++. La intención es que el código esté compilado como C o todo compilado como C ++.
Kaz
15

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 haya variables declaradas como auto
  3. Luego compílelo usando un compilador de C ++ con -Wally -std=c++11. Solucione todos los errores y advertencias.
  4. Ahora compile usando el compilador de C nuevamente. Su programa ahora debe compilarse sin previo aviso y contener menos errores.

Este procedimiento le permite aprovechar la estricta verificación de tipos de C ++, reduciendo así la cantidad de errores. En particular, este procedimiento te obliga a incluir stdlib.ho obtendrás

malloc no fue declarado dentro de este alcance

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

conversión no válida de void*aT*

o cual 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 ]

Tenga en cuenta que los segundos inconvenientes deberían desaparecer en el caso ideal cuando se usa el subconjunto común a C junto con la característica polimórfica estática .

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

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
user877329
fuente
18
Use un compilador de C para el código C. Use un compilador de C ++ para el código de C ++. Sin peros, sin peros. Reescribir su código C en C ++ es algo completamente distinto, y puede, o puede que no valga la pena, el tiempo y los riesgos.
Toby Speight
2
Me gustaría agregar un consejo a @TobySpeight: si necesita usar el código C en un proyecto C ++, generalmente puede compilar el código C como C (por ejemplo gcc -c c_code.c), el código C ++ como C ++ (por ejemplo g++ -c cpp_code.cpp), y luego vincularlos (por ejemplo, gcc c_code.o cpp_code.oo viceversa, según las dependencias del proyecto). Ahora no debería haber ninguna razón para privarse de las características agradables de cualquiera de los idiomas ...
autista
1
@ user877329 Es una alternativa más sensata a agregar minuciosamente conversiones al código que reducen la legibilidad del código, solo por el hecho de ser "compatibles con C ++".
autista
1
Probablemente, la principal ventaja en este contexto es que C le permite escribir p = malloc(sizeof(*p));, lo que no necesita cambiar en primer lugar si pcambia a un nombre de tipo diferente. La "ventaja" propuesta de la conversión es que obtienes un error de compilación si pes del tipo incorrecto, pero es aún mejor si simplemente funciona.
Peter Cordes
1
Me gustaría mencionar que escribir en C puede ser necesario cuando se apunta a plataformas que carecen de compiladores C ++ adecuados. Las excepciones y las plantillas son características que generalmente ayudan a c ++ a generar código más pequeño y / o más eficiente, mientras que el polimorfismo de tiempo de ejecución en C ++ es mayormente equivalente a C.
user7860670
15

No, no emites el resultado de malloc().

En general, no lanzas hacia o desdevoid * .

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

Pero hay un razón mucho más fuerte para no introducir lanzamientos de puntero innecesarios:

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

Un objeto tendrá 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 con signo o sin signo correspondiente al tipo efectivo del objeto,
- un tipo que es el tipo con signo o sin signo correspondiente a una versión calificada del tipo efectivo del objeto,
- un tipo agregado o de unión que incluye uno de los tipos antes mencionados entre sus miembros (incluido, recursivamente, un miembro de una unión agregada o contenida), o
- un tipo de carácter.

Esto también se conoce como la estricta regla de alias . 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 también es:

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 punteros elenco, 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 emitido en su código es un lugar donde debe verificar su validez . Por lo tanto, nunca escribe un molde de puntero innecesario.

tl; dr

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


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 variada, por lo que las conversiones implícitas no funcionan.

  • En C ++, la situación es diferente. La conversión de tipos de puntero es algo común (y correcto) 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.


fuente
1
En tus ejemplos evitas el vacío *. Hay una diferencia entre emitir de doble * a int * y viceversa. malloc devuelve el puntero alineado al tipo estándar más grande para que no se rompan las reglas de alias, incluso si alguien lanza este puntero alineado a otro tipo.
P__J__
Aliasing no tiene nada que ver con la alineación y para el resto de tu comentario, obviamente no entendiste el punto.
@PeterJ: por si acaso, el punto es evitar un lanzamiento de puntero innecesario, por lo que no parece un código al que hay que prestar especial atención.
El problema de alias estricto realmente no tiene nada que ver con los punteros nulos. Para obtener errores causados ​​por infracciones estrictas de alias, debe desreferenciar los datos señalados. Y dado que no puede desreferenciar un puntero vacío, dichos errores no están, por definición, relacionados con el puntero vacío, sino con algo más.
Lundin
Más bien, tendrías que hacer una regla para prohibir todos los lanzamientos de punteros. Pero entonces, ¿cómo escribiría cosas como rutinas de serialización y programación relacionada con el hardware? Cosas que son la fuerza de C. Tales modelos están bien si sabes lo que estás haciendo.
Lundin
15

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 obtiene el tipo incorrecto, obtendría una conversión implícita entre punteros no vacíos, 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 malloceso toma menos argumentos que calloc. Simplemente agregue 0para obtener memoria de inicialización cero. El código se puede compilar con un compilador de C ++ sin cambios.

proski
fuente
12

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


fuente
9

El concepto detrás del puntero void es que se puede convertir a cualquier tipo de datos, por eso malloc devuelve void. 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 limpio el código y ayuda a depurar

iec2011007
fuente
11
" No es obligatorio, aunque debes hacerlo ". ¡Creo que hay una contradicción allí!
Toby Speight
55
Creo que deberías leer esta publicación a alguien y ver si entienden lo que estás tratando de decir. Luego, vuelva a escribirlo, dejando en claro lo que quiere decir. Realmente no puedo entender cuál es tu respuesta.
Bill Woodger
9

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

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

dhana govindarajan
fuente
9
  1. Como se indicó, no es necesario para C, sino para C ++.

  2. La inclusión del elenco puede permitir que un programa o función C se compile como C ++.

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

  4. Pero si lanza, puede ocultar un error si olvidó incluir stdlib.h . Esto puede causar bloqueos (o, lo que es peor, no causar un bloqueo hasta más tarde en una 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.

Mohit
fuente
7

La conversión de malloc es innecesaria en C pero obligatoria en C ++.

La fundición es innecesaria en C debido a:

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

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

Aashish
fuente
0

Para mí, la conclusión y la conclusión aquí es que el lanzamiento mallocen C NO es totalmente necesario, pero si lo hace, no afectará, mallocya mallocque aún le asignará el espacio de memoria bendita solicitado. Otra razón para llevar a casa es la razón o una de las razones por las que las personas hacen casting y esto es para permitirles compilar el mismo programa en C o C ++.

Puede haber otras razones, pero otras razones, casi con certeza, lo llevarían a serios problemas tarde o temprano.

pasignaturaleza
fuente
0

Puede, pero no necesita convertir en C. Debe emitir si ese código se compila como C ++.

ivan.ukr
fuente