¿Por qué el uso de alloca () no se considera una buena práctica?

alloca() asigna memoria de Stack en lugar de Heap, que es el caso en malloc() . Entonces, cuando regreso de la rutina, la memoria se libera. Entonces, en realidad, esto resuelve mi problema de liberar la memoria asignada dinámicamente. La liberación de la memoria asignada a través de malloc() es un gran dolor de cabeza y si se pierde de alguna manera conduce a problemas de memoria de todo tipo.

¿Por qué se desaconseja el uso de alloca() a pesar de las características anteriores?

La respuesta está ahí en la página man (al menos en Linux):

VALOR DEVUELTO La función alloca () devuelve un puntero al comienzo del espacio asignado. Si la asignación causa el desbordamiento de la stack, el comportamiento del progtwig no está definido.

Lo cual no quiere decir que nunca debería ser usado. Uno de los proyectos de OSS en el que trabajo lo usa extensivamente, y mientras no lo alloca ( alloca valores enormes), está bien. Una vez que pasa la marca de “pocos cientos de bytes”, es hora de utilizar malloc y amigos, en su lugar. Todavía puede obtener fallas de asignación, pero al menos tendrá alguna indicación del error en lugar de simplemente eliminar la stack.

Uno de los errores más memorables que tuve fue que ver con una función en línea que usaba alloca . Se manifestó como un desbordamiento de stack (porque asigna en la stack) en puntos aleatorios de la ejecución del progtwig.

En el archivo de encabezado:

 void DoSomething() { wchar_t* pStr = alloca(100); //...... } 

En el archivo de implementación:

 void Process() { for (i = 0; i < 1000000; i++) { DoSomething(); } } 

Entonces, lo que sucedió fue que el comstackdor DoSomething función DoSomething y todas las asignaciones de stack estaban sucediendo dentro de la función Process() y, por lo tanto, explotaban la stack. En mi defensa (y yo no fui el que encontró el problema, tuve que ir a llorar a uno de los desarrolladores sénior cuando no pude arreglarlo), no era alloca directo, era uno de cadena ATL macros de conversión.

Entonces, la lección es: no use alloca en funciones que piense que podrían estar en línea.

Una vieja pregunta pero nadie mencionó que debería ser reemplazada por arreglos de longitud variable.

 char arr[size]; 

en lugar de

 char *arr=alloca(size); 

Está en el estándar C99 y existía como extensión de comstackción en muchos comstackdores.

alloca () es muy útil si no puede usar una variable local estándar porque su tamaño debería determinarse en tiempo de ejecución y puede garantizar que el puntero que obtenga de alloca () NUNCA se utilizará después de que esta función regrese .

Puedes estar bastante seguro si

  • no devuelva el puntero ni nada que lo contenga.
  • no almacene el puntero en ninguna estructura asignada en el montón
  • no dejes que ningún otro hilo use el puntero

El peligro real proviene de la posibilidad de que alguien más viole estas condiciones en algún momento más tarde. Con eso en mente, es genial para pasar memorias intermedias a funciones que les dan formato de texto 🙂

Como se señala en esta publicación del grupo de noticias , existen algunas razones por las que el uso de alloca puede considerarse difícil y peligroso:

  • No todos los comstackdores admiten alloca .
  • Algunos comstackdores interpretan el comportamiento previsto de alloca de alloca diferente, por lo que la portabilidad no está garantizada ni siquiera entre los comstackdores que la admiten.
  • Algunas implementaciones tienen errores.

Un problema es que no es estándar, aunque es ampliamente compatible. En igualdad de condiciones, siempre usaría una función estándar en lugar de una extensión de comstackción común.

Todavía se desaconseja el uso de aloca, ¿por qué?

No percibo tal consenso. Muchos profesionales fuertes; algunas desventajas:

  • C99 proporciona matrices de longitud variable, que a menudo se utilizarán de preferencia, ya que la notación es más consistente con las matrices de longitud fija y el general intuitivo
  • muchos sistemas tienen menos memoria / espacio de direcciones disponibles para la stack que para el montón, lo que hace que el progtwig sea ligeramente más susceptible al agotamiento de la memoria (a través del desbordamiento de la stack): esto puede verse como algo bueno o malo: uno de las razones por las que la stack no crece automáticamente de la manera que lo hace el montón es para evitar que los progtwigs fuera de control tengan tanto impacto adverso en toda la máquina
  • cuando se utiliza en un ámbito más local (como un while o for bucle) o en varios ámbitos, la memoria se acumula por iteración / scope y no se libera hasta que finaliza la función: esto contrasta con las variables normales definidas en el scope de una estructura de control (por ejemplo, for {int i = 0; i < 2; ++i) { X } se acumularía la memoria alloca solicitada en X, pero la memoria para una matriz de tamaño fijo se reciclaría por iteración).
  • los comstackdores modernos normalmente no inline funciones que invocan alloca , pero si las fuerza, entonces la alloca ocurrirá en el contexto de las personas que llaman (es decir, la stack no se liberará hasta que la persona que llama vuelva)
  • Hace mucho tiempo alloca pasó de ser una característica no portátil / hack a una extensión estandarizada, pero puede persistir cierta percepción negativa
  • el tiempo de vida está ligado al scope de la función, que puede o no adecuarse mejor al progtwigdor que el control explícito de malloc
  • tener que usar malloc alienta a pensar en la desasignación, si se gestiona a través de una función de envoltura (por ejemplo, WonderfulObject_DestructorFree(ptr) ), la función proporciona un punto para las operaciones de limpieza de implementación (como cerrar descriptores de archivos, liberar punteros internos o realizar algún registro) sin cambios explícitos al código del cliente: a veces es un buen modelo para adoptar consistentemente
    • en este estilo de progtwigción pseudo-OO, es natural querer algo como WonderfulObject* p = WonderfulObject_AllocConstructor(); - eso es posible cuando el "constructor" es una función que devuelve la memoria malloc -ed (ya que la memoria permanece asignada después de que la función devuelve el valor que se almacenará en p ), pero no si el "constructor" usa alloca
      • una versión macro de WonderfulObject_AllocConstructor podría lograr esto, pero "las macros son malas", ya que pueden entrar en conflicto entre sí y con código que no sea de macro y crear sustituciones involuntarias y los consiguientes problemas difíciles de diagnosticar
    • las operaciones faltantes que faltan pueden ser detectadas por ValGrind, Purify, etc., pero las llamadas "destructor" faltantes no siempre se pueden detectar en absoluto, un beneficio muy tenue en términos de aplicación del uso previsto; algunas implementaciones de alloca() (como las de GCC) usan una macro inline para alloca() , por lo que la sustitución de una biblioteca de diagnóstico de uso de memoria no es posible de la forma en que es para malloc / realloc / free (por ejemplo, cerca eléctrica)
  • algunas implementaciones tienen problemas sutiles: por ejemplo, desde la página de manual de Linux:

En muchos sistemas alloca () no puede usarse dentro de la lista de argumentos de una llamada a función, porque el espacio de stack reservado por alloca () aparecería en la stack en el medio del espacio para los argumentos de la función.


Sé que esta pregunta está etiquetada C, pero como progtwigdor de C ++ pensé que usaría C ++ para ilustrar la utilidad potencial de alloca : el código de abajo (y aquí en ideone ) crea un vector rastreando tipos polimórficos de diferentes tamaños que se asignan a la stack ( con el tiempo de vida vinculado al retorno de la función) en lugar del montón asignado.

 #include  #include  #include  struct Base { virtual ~Base() { } virtual int to_int() const = 0; }; struct Integer : Base { Integer(int n) : n_(n) { } int to_int() const { return n_; } int n_; }; struct Double : Base { Double(double n) : n_(n) { } int to_int() const { return -n_; } double n_; }; inline Base* factory(double d) __attribute__((always_inline)); inline Base* factory(double d) { if ((double)(int)d != d) return new (alloca(sizeof(Double))) Double(d); else return new (alloca(sizeof(Integer))) Integer(d); } int main() { std::vector numbers; numbers.push_back(factory(29.3)); numbers.push_back(factory(29)); numbers.push_back(factory(7.1)); numbers.push_back(factory(2)); numbers.push_back(factory(231.0)); for (std::vector::const_iterator i = numbers.begin(); i != numbers.end(); ++i) { std::cout < < *i << ' ' << (*i)->to_int() < < '\n'; (*i)->~Base(); // optionally / else Undefined Behaviour iff the // program depends on side effects of destructor } } 

Todas las demás respuestas son correctas. Sin embargo, si lo que desea asignar usando alloca() es razonablemente pequeño, creo que es una buena técnica que es más rápida y más conveniente que usar malloc() o de otro modo.

En otras palabras, alloca( 0x00ffffff ) es peligroso y es probable que cause desbordamiento, exactamente tanto como char hugeArray[ 0x00ffffff ]; es. Sé prudente y razonable y estarás bien.

Todo el mundo ya ha señalado la gran cosa que es el comportamiento indefinido potencial de un desbordamiento de stack, pero debo mencionar que el entorno de Windows tiene un gran mecanismo para atrapar esto utilizando excepciones estructuradas (SEH) y páginas de guardia. Como la stack solo crece según sea necesario, estas páginas de guardia residen en áreas que no están asignadas. Si asigna en ellos (desbordando la stack) se lanza una excepción.

Puede ver esta excepción SEH y llamar a _resetstkoflw para reiniciar la stack y continuar de manera feliz. No es ideal, pero es otro mecanismo para, al menos, saber que algo ha salido mal cuando la cosa golpea al ventilador. * nix podría tener algo similar que yo no sepa.

Recomiendo encapsular su tamaño de asignación máximo envolviendo alloca y rastreándolo internamente. Si fueras realmente duro al respecto, podrías lanzar centinelas de scope en la parte superior de tu función para rastrear cualquier asignación de alloca en el scope de la función y verificar la cordura con el monto máximo permitido para tu proyecto.

Además, además de no permitir memory leaks, alloca no causa la fragmentación de la memoria, que es bastante importante. No creo que alloca sea una mala práctica si la usas inteligentemente, lo cual es básicamente cierto para todo. 🙂

Este es el por qué:

 char x; char *y=malloc(1); char *z=alloca(&x-y); *z = 1; 

No es que alguien escriba este código, pero el argumento de tamaño que está pasando a alloca casi con certeza proviene de algún tipo de entrada, que podría apuntar maliciosamente a que su progtwig alloca algo así. Después de todo, si el tamaño no se basa en la entrada o no tiene la posibilidad de ser grande, ¿por qué no declaraste un pequeño buffer local de tamaño fijo?

Prácticamente todo el código que usa alloca y / o Vlas C99 tiene errores serios que provocarán fallas (si tienes suerte) o un compromiso de privilegio (si no tienes tanta suerte).

alloca () es agradable y eficiente … pero también está profundamente roto.

  • comportamiento de ámbito roto (scope de función en lugar de scope de bloque)
  • use inconsistente con malloc (no se debe liberar el puntero alloca () , de aquí en adelante hay que rastrear de dónde provienen los punteros para liberar () solo aquellos que obtuvo con malloc () )
  • mal comportamiento cuando también usa la línea interior (el scope a veces va a la función de llamante dependiendo de si el destinatario está en línea o no).
  • sin límite de stack
  • comportamiento indefinido en caso de falla (no devuelve NULL como malloc … y lo que significa el fracaso, ya que no comprueba los límites de stack de todos modos …)
  • no ansi estándar

En la mayoría de los casos, puede reemplazarlo utilizando variables locales y tamaño majorant. Si se usa para objetos grandes, ponerlos en el montón suele ser una idea más segura.

Si realmente lo necesita C puede usar VLA (no vla en C ++, muy mal). Son mucho mejores que alloca () con respecto al comportamiento del scope y la coherencia. Como lo veo, VLA es un tipo de alloca () hecho a la derecha.

Por supuesto, una estructura local o una matriz que utilice un cuidador del espacio necesario es aún mejor, y si usted no tiene dicha asignación de montón Majorant usando malloc simple () es probablemente cuerdo. No veo ningún caso de uso sensato en el que realmente necesite alloca () o VLA.

Muchas respuestas interesantes a esta “vieja” pregunta, incluso algunas respuestas relativamente nuevas, pero no encontré ninguna que mencione esto ….

Cuando se usa correctamente y con cuidado, el uso consistente de alloca() (quizás en toda la aplicación) para manejar pequeñas asignaciones de longitud variable (o VLAs C99, donde estén disponibles) puede conducir a un menor crecimiento general de la stack que una implementación de otra manera equivalente utilizando matrices locales de gran tamaño de longitud fija. Así que alloca() puede ser bueno para tu stack si lo usas con cuidado.

Encontré esa cita en … OK, hice esa cita. Pero en serio, piénselo …

@j_random_hacker está muy acertado en sus comentarios en otras respuestas: evitar el uso de alloca() a favor de arreglos locales de gran tamaño no hace que tu progtwig sea más seguro frente a desbordamientos de stack (a menos que tu comstackdor sea lo suficientemente viejo como para permitir el uso de alloca() en cuyo caso debe actualizar, o a menos que use alloca() dentro de los bucles, en cuyo caso debe … no usar alloca() dentro de los bucles).

He trabajado en entornos de escritorio / servidor y sistemas integrados. Una gran cantidad de sistemas integrados no usan ningún tipo de stack (ni siquiera se vinculan como soporte), por razones que incluyen la percepción de que la memoria asignada dinámicamente es mala debido a los riesgos de memory leaks en una aplicación que nunca Alguna vez se reinicia durante años a la vez, o la justificación más razonable de que la memoria dinámica es peligrosa porque no se puede saber con certeza que una aplicación nunca fragmentará su stack hasta el punto del agotamiento de la memoria falsa. Entonces los progtwigdores incrustados se quedan con pocas alternativas.

alloca() (o VLA) puede ser la herramienta adecuada para el trabajo.

He visto una y otra vez que un progtwigdor crea un búfer asignado por la stack “lo suficientemente grande como para manejar cualquier caso posible”. En un árbol de llamadas nested profundamente, el uso repetido de ese patrón (anti -?) Conduce a un uso de stack exagerado. (Imagine un árbol de llamadas de 20 niveles de profundidad, donde en cada nivel por diferentes razones, la función sobreajusta ciegamente un búfer de 1024 bytes “solo para estar seguros” cuando generalmente solo usará 16 o menos de ellos, y solo en muy los casos raros pueden usar más). Una alternativa es usar alloca() o VLA y asignar solo el espacio de stack que su función necesite, para evitar cargar innecesariamente la stack. Es de esperar que cuando una función en el árbol de llamadas necesite una asignación mayor que la normal, otros en el árbol de llamadas sigan usando sus pequeñas asignaciones normales, y el uso general de la stack de aplicaciones sea significativamente menor que si cada función asignara un buffer local a ciegas .

Pero si elige usar alloca()

Según otras respuestas en esta página, parece que los VLA deben ser seguros (no incluyen asignaciones de stack si se llaman desde un bucle), pero si usa alloca() , tenga cuidado de no usarlo dentro de un bucle , y asegúrese de que su función no se pueda alinear si existe la posibilidad de que se invoque dentro del ciclo de otra función.

Un lugar donde alloca() es especialmente peligroso que malloc() es el kernel – kernel de un sistema operativo típico tiene un espacio de stack de tamaño fijo codificado en uno de sus encabezados; no es tan flexible como la stack de una aplicación. Hacer una llamada a alloca() con un tamaño injustificado puede causar la falla del kernel. Ciertos comstackdores advierten el uso de alloca() (e incluso de los VLA para ese asunto) bajo ciertas opciones que deberían alloca() al comstackr un código de núcleo; aquí, es mejor asignar memoria en el montón que no está fijada por un disco rígido. límite codificado.

Tristemente, la asombrosa alloca() falta en la casi increíble tcc. Gcc tiene alloca() .

  1. Siembra la semilla de su propia destrucción. Con el regreso como el destructor.

  2. Al igual que malloc() devuelve un puntero no válido en el error que segfault en los sistemas modernos con una MMU (y con suerte reiniciar los que no).

  3. A diferencia de las variables automáticas, puede especificar el tamaño en tiempo de ejecución.

Funciona bien con recursividad. Puede usar variables estáticas para lograr algo similar a la recursión de cola y usar solo algunas otras para pasar información a cada iteración.

Si empuja demasiado profundo, tiene asegurada una segfault (si tiene una MMU).

Tenga en cuenta que malloc() no ofrece más ya que devuelve NULL (que también segfault si está asignado) cuando el sistema está sin memoria. Es decir, todo lo que puedes hacer es pagar fianza o simplemente intentar asignarlo de cualquier forma.

Para usar malloc() uso globales y les asigno NULL. Si el puntero no es NULL, lo libero antes de usar malloc() .

También puede usar realloc() como caso general si quiere copiar cualquier información existente. realloc() comprobar el puntero antes de calcular si va a copiar o concatenar después de realloc() .

3.2.5.2 Ventajas de alloca

If you accidentally write beyond the block allocated with alloca (due to a buffer overflow for example), then you will overwrite the return address of your function, because that one is located “above” on the stack, ie after your allocated block.

_alloca block on the stack

The consequence of this is two-fold:

  1. The program will crash spectacularly and it will be impossible to tell why or where it crashed (stack will most likely unwind to a random address due to the overwritten frame pointer).

  2. It makes buffer overflow many times more dangerous, since a malicious user can craft a special payload which would be put on the stack and can therefore end up executed.

In contrast, if you write beyond a block on the heap you “just” get heap corruption. The program will probably terminate unexpectedly but will unwind the stack properly, thereby reducing the chance of malicious code execution.

I don’t think anyone has mentioned this: Use of alloca in a function will hinder or disable some optimizations that could otherwise be applied in the function, since the compiler cannot know the size of the function’s stack frame.

For instance, a common optimization by C compilers is to eliminate use of the frame pointer within a function, frame accesses are made relative to the stack pointer instead; so there’s one more register for general use. But if alloca is called within the function, the difference between sp and fp will be unknown for part of the function, so this optimization cannot be done.

Given the rarity of its use, and its shady status as a standard function, compiler designers quite possibly disable any optimization that might cause trouble with alloca, if would take more than a little effort to make it work with alloca.

One pitfall with alloca is that longjmp rewinds it.

That is to say, if you save a context with setjmp , then alloca some memory, then longjmp to the context, you may lose the alloca memory (without any sort of notice). The stack pointer is back where it was and so the memory is no longer reserved; if you call a function or do another alloca , you will clobber the original alloca .

Incidentally, this provides a plausible mechanism for deliberately freeing memory that was allocated with alloca .

This isn’t documented anywhere; of course the ISO C description of setjmp will say nothing about interactions with alloca , since it describes no such function. Implementations that provide alloca tend not to document this either.

The focus is usually on the concept that alloca memory is associated with a function activation, not with any block; that multiple invocations of alloca just grab more stack memory which is all released when the function terminates. Not so; the memory is actually associated with the procedure context. When the context is restred with longjmp , so is the prior alloca state.

I have run into this which is how I know.

Processes only have a limited amount of stack space available – far less than the amount of memory available to malloc() .

By using alloca() you dtwigtically increase your chances of getting a Stack Overflow error (if you’re lucky, or an inexplicable crash if you’re not).

Not very pretty, but if performance really matter, you could preallocate some space on the stack.

If you already now the max size of the memory block your need and you want to keep overflow checks, you could do something like :

 void f() { char array_on_stack[ MAX_BYTES_TO_ALLOCATE ]; SomeType *p = (SomeType *)array; (...) } 

Actually, alloca is not guaranteed to use the stack. Indeed, the gcc-2.95 implementation of alloca allocates memory from the heap using malloc itself. Also that implementation is buggy, it may lead to a memory leak and to some unexpected behavior if you call it inside a block with a further use of goto. Not, to say that you should never use it, but some times alloca leads to more overhead than it releaves frome.

The alloca function is great and and all the naysayers are simply spreading FUD.

 void foo() { int x = 50000; char array[x]; char *parray = (char *)alloca(x); } 

Array and parray are EXACTLY the same with EXACTLY the same risks. Saying one is better than another is a syntactic choice, not a technical one.

As for choosing stack variables vs heap variables, there are a LOT of advantages to long running programs using stack over heap for variables with in-scope lifetimes. You avoid heap fragmentation and you can avoid growing your process space with unused (unusable) heap space. You don’t need to clean it up. You can control the stack allocation on the process.

Why is this bad?

IMHO, alloca is considered bad practice because everybody is afraid of exhausting the stack size limit.

I learned much by reading this thread and some other links:

I use alloca mainly to make my plain C files comstackble on msvc and gcc without any change, C89 style, no #ifdef _MSC_VER, etc.

Gracias ! This thread made me sign up to this site 🙂

In my opinion, alloca(), where available, should be used only in a constrained manner. Very much like the use of “goto”, quite a large number of otherwise reasonable people have strong aversion not just to the use of, but also the existence of, alloca().

For embedded use, where the stack size is known and limits can be imposed via convention and analysis on the size of the allocation, and where the compiler cannot be upgraded to support C99+, use of alloca() is fine, and I’ve been known to use it.

When available, VLAs may have some advantages over alloca(): The compiler can generate stack limit checks that will catch out-of-bounds access when array style access is used (I don’t know if any compilers do this, but it can be done), and analysis of the code can determine whether the array access expressions are properly bounded. Note that, in some programming environments, such as automotive, medical equipment, and avionics, this analysis has to be done even for fixed size arrays, both automatic (on the stack) and static allocation (global or local).

On architectures that store both data and return addresses/frame pointers on the stack (from what I know, that’s all of them), any stack allocated variable can be dangerous because the address of the variable can be taken, and unchecked input values might permit all sorts of mischief.

Portability is less of a concern in the embedded space, however it is a good argument against use of alloca() outside of carefully controlled circumstances.

Outside of the embedded space, I’ve used alloca() mostly inside logging and formatting functions for efficiency, and in a non-recursive lexical scanner, where temporary structures (allocated using alloca() are created during tokenization and classification, then a persistent object (allocated via malloc()) is populated before the function returns. The use of alloca() for the smaller temporary structures greatly reduces fragmentation when the persistent object is allocated.

Most answers here largely miss the point: there’s a reason why using _alloca() is potentially worse than merely storing large objects in the stack.

The main difference between automatic storage and _alloca() is that the latter suffers from an additional (serious) problem: the allocated block is not controlled by the compiler , so there’s no way for the compiler to optimize or recycle it.

Comparar:

 while (condition) { char buffer[0x100]; // Chill. /* ... */ } 

con:

 while (condition) { char* buffer = _alloca(0x100); // Bad! /* ... */ } 

The problem with the latter should be obvious.