¿Cuándo debería usar malloc en C y cuándo no?

94

Entiendo cómo funciona malloc (). Mi pregunta es, veré cosas como esta:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Omití la comprobación de errores en aras de la brevedad. Mi pregunta es, ¿no puede hacer lo anterior inicializando un puntero a algún almacenamiento estático en la memoria? quizás:

char *some_memory = "Hello World";

¿En qué punto realmente necesita asignar la memoria usted mismo en lugar de declarar / inicializar los valores que necesita retener?

randombits
fuente
5
Re: Omití la comprobación de errores en aras de la brevedad . Desafortunadamente, muchos programadores omiten la comprobación de errores porque no se dan cuenta de que malloc()puede fallar.
Andrew

Respuestas:

132
char *some_memory = "Hello World";

está creando un puntero a una constante de cadena. Eso significa que la cadena "Hello World" estará en algún lugar de la parte de solo lectura de la memoria y solo tiene un puntero hacia ella. Puede usar la cadena como de solo lectura. No puede hacerle cambios. Ejemplo:

some_memory[0] = 'h';

Está buscando problemas.

Por otra parte

some_memory = (char *)malloc(size_to_allocate);

está asignando una matriz de caracteres (una variable) y algunos puntos de memoria a esa memoria asignada. Ahora, esta matriz es tanto de lectura como de escritura. Ahora puedes hacer:

some_memory[0] = 'h';

y el contenido de la matriz cambia a "hola mundo"

codaddict
fuente
19
Solo para aclarar, por mucho que me guste esta respuesta (le di +1), puede hacer lo mismo sin malloc () simplemente usando una matriz de caracteres. Algo como: char some_memory [] = "Hola"; alguna_memoria [0] = 'W'; también funcionará.
randombits
19
Tienes razón. Usted puede hacer eso. Cuando usa malloc (), la memoria se asigna dinámicamente en tiempo de ejecución, por lo que no necesita arreglar el tamaño de la matriz en tiempo de compilación, también puede hacer que crezca o se reduzca usando realloc () Ninguna de estas cosas se puede hacer cuando lo hace: char some_memory [] = "Hola"; Aquí, aunque puede cambiar el contenido de la matriz, su tamaño es fijo. Entonces, dependiendo de sus necesidades, use cualquiera de las tres opciones: 1) puntero a char const 2) matriz asignada dinámicamente 3) tamaño fijo, matriz asignada en tiempo de compilación.
codaddict
Para enfatizar que es de solo lectura, debe escribir const char *s = "hi";¿No es esto realmente requerido por el estándar?
Hasta Theis
@ Hasta, no, porque declaró un puntero inicializado a la dirección base de la cadena literal "hi". s se pueden reasignar perfectamente legalmente para apuntar a un carácter no constante. Si desea un puntero constante a una cadena de solo lectura, necesitaconst char const* s;
Rob11311
38

Para ese ejemplo exacto, malloc es de poca utilidad.

La razón principal por la que se necesita malloc es cuando tiene datos que deben tener una vida útil diferente del alcance del código. Su código llama a malloc en una rutina, almacena el puntero en algún lugar y finalmente llama a free en una rutina diferente.

Una razón secundaria es que C no tiene forma de saber si queda suficiente espacio en la pila para una asignación. Si su código necesita ser 100% robusto, es más seguro usar malloc porque entonces su código puede saber que la asignación falló y manejarla.

R Samuel Klatchko
fuente
4
Los ciclos de vida de la memoria, y la cuestión relacionada de cuándo y cómo desasignarlos, son un tema importante con muchas bibliotecas y componentes de software comunes. Por lo general, tienen una regla bien documentada: "Si pasas un puntero a esta de mis rutinas, debes haberlo bloqueado. Lo seguiré y lo liberaré cuando termine con él. " Una fuente común de errores desagradables es pasar un puntero a la memoria asignada estáticamente a dicha biblioteca. Cuando la biblioteca intenta liberarlo (), el programa falla. Recientemente pasé mucho tiempo arreglando un error como el que escribió otra persona.
Bob Murphy
¿Está diciendo que la única vez que se usa malloc () de manera práctica, es cuando hay un segmento de código que se llamará varias veces durante la vida del programa que se llamará varias veces y necesita ser 'limpiado', ya que malloc () va acompañado de free ()? Por ejemplo, en un juego como la rueda de la fortuna, ¿dónde después de adivinar y poner la entrada en una matriz de caracteres designada, la matriz del tamaño de malloc () se puede liberar para la siguiente suposición?
Smith Will Suffice
La vida útil de los datos es de hecho la verdadera razón para utilizar malloc. Suponga que un tipo de datos abstracto está representado por un módulo, declara un tipo de lista y rutinas para agregar / eliminar elementos de la lista. Esos valores de los elementos deben copiarse en la memoria asignada dinámicamente.
Rob11311
@Bob: esos errores desagradables, hacen que la convención de que el asignador libera memoria sea muy superior, después de todo, es posible que la esté reciclando. Suponga que asignó memoria con calloc para mejorar la ubicación de las referencias, que expone la naturaleza rota de esas bibliotecas, porque necesita llamar a free solo una vez para todo el bloque. Afortunadamente, no he tenido que usar bibliotecas que especifiquen que la memoria sea 'malloc-ed', no es una tradición POSIX y muy probablemente se consideraría un error. Si "saben" que tienes que usar malloc, ¿por qué la rutina de la biblioteca no lo hace por ti?
Rob11311
17

malloc es una herramienta maravillosa para asignar, reasignar y liberar memoria en tiempo de ejecución, en comparación con declaraciones estáticas como su ejemplo de Hola mundo, que se procesan en tiempo de compilación y, por lo tanto, no se pueden cambiar de tamaño.

Por lo tanto, Malloc siempre es útil cuando se trata de datos de tamaño arbitrario, como leer el contenido de un archivo o manejar sockets y no se sabe la longitud de los datos que se deben procesar.

Por supuesto, en un ejemplo trivial como el que proporcionó, malloc no es la "herramienta correcta para el trabajo correcto" mágica, pero para casos más complejos (crear una matriz de tamaño arbitrario en tiempo de ejecución, por ejemplo), es la única forma de Vamos.

Moritz
fuente
7

Si no conoce el tamaño exacto de la memoria que necesita usar, necesita una asignación dinámica ( malloc). Un ejemplo podría ser cuando un usuario abre un archivo en su aplicación. Deberá leer el contenido del archivo en la memoria, pero, por supuesto, no conoce el tamaño del archivo de antemano, ya que el usuario selecciona el archivo en el momento, en tiempo de ejecución. Entonces, básicamente, lo necesita malloccuando no conoce el tamaño de los datos con los que está trabajando de antemano. Al menos esa es una de las principales razones para consumir malloc. En su ejemplo con una cadena simple cuyo tamaño ya conoce en el momento de la compilación (además de que no desea modificarlo), no tiene mucho sentido asignar eso dinámicamente.


Un poco fuera de tema, pero ... debes tener mucho cuidado de no crear pérdidas de memoria al usar malloc. Considere este código:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

¿Ves lo que está mal con este código? Hay una declaración de retorno condicional entre mallocyfree . Puede parecer bien al principio, pero piénselo. Si hay un error, volverá sin liberar la memoria que asignó. Esta es una fuente común de pérdidas de memoria.

Por supuesto, este es un ejemplo muy simple, y es muy fácil ver el error aquí, pero imagina cientos de líneas de código llenas de punteros, mallocs,free s, y todo tipo de manejo de errores. Las cosas pueden complicarse mucho muy rápido. Esta es una de las razones por las que prefiero C ++ moderno sobre C en los casos aplicables, pero ese es un tema completamente diferente.

Por lo tanto, siempre que lo use malloc, asegúrese siempre de que su memoria tenga las mismasfree posible.

Adam10603
fuente
¡Excelente ejemplo! Así se hace ^ _ ^
Musa Al-hassy
6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

es ilegal, los literales de cadena son const .

Esto asignará una matriz de caracteres de 12 bytes en la pila o globalmente (dependiendo de dónde se declare).

char some_memory[] = "Hello World";

Si desea dejar espacio para una mayor manipulación, puede especificar que el tamaño de la matriz sea mayor. (Sin embargo, no ponga 1 MB en la pila).

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);
efímero
fuente
5

Una razón por la que es necesario asignar la memoria es si desea modificarla en tiempo de ejecución. En ese caso, se puede utilizar un malloc o un búfer en la pila. El ejemplo simple de asignar "Hola mundo" a un puntero define la memoria que "normalmente" no se puede modificar en tiempo de ejecución.

Mark Wilkins
fuente