Por que o uso de alloca () não é considerado uma boa prática?


401

alloca()aloca memória na pilha e não na pilha, como no caso de malloc(). Então, quando eu volto da rotina, a memória é liberada. Então, na verdade, isso resolve meu problema de liberar memória alocada dinamicamente. A liberação de memória alocada malloc()é uma grande dor de cabeça e, se perdida de alguma forma, leva a todos os tipos de problemas de memória.

Por que o uso de alloca()desencorajado apesar dos recursos acima?


40
Apenas uma nota rápida. Embora essa função possa ser encontrada na maioria dos compiladores, ela não é requerida pelo padrão ANSI-C e, portanto, pode limitar a portabilidade. Outra coisa é que você não deve! free () o ponteiro que você recebe e é liberado automaticamente depois que você sai da função.
merkuro

9
Além disso, uma função com alloca () não será incorporada se declarada como tal.
Justicle

2
@ Artigo, você pode fornecer uma explicação? Estou muito curioso o que está por trás deste comportamento
migajek

47
Esqueça todo o barulho sobre portabilidade, não há necessidade de ligar free(o que é obviamente uma vantagem), a não capacidade de incorporá-lo (obviamente, as alocações de heap são muito mais pesadas) e etc. O único motivo a ser evitado allocaé o tamanho grande. Ou seja, desperdiçar toneladas de memória da pilha não é uma boa ideia, além de você ter a chance de um estouro de pilha. Se for esse o caso - considere usar malloca/freea
valdo

5
Outro aspecto positivo allocaé que a pilha não pode ser fragmentada como a pilha. Isso pode ser útil para aplicativos de estilo duradouro em tempo real ou mesmo críticos para a segurança, uma vez que a WCRU pode ser analisada estaticamente sem recorrer a conjuntos de memória personalizados com seu próprio conjunto de problemas (sem localidade temporal, recurso insuficiente) usar).
Andreas

Respostas:


245

A resposta está aí na manpágina (pelo menos no Linux ):

VALOR DE RETORNO A função alloca () retorna um ponteiro para o início do espaço alocado. Se a alocação causar estouro de pilha, o comportamento do programa será indefinido.

O que não quer dizer que nunca deva ser usado. Um dos projetos de OSS em que trabalho o usa extensivamente e, desde que você não esteja abusando (com allocavalores enormes), tudo bem. Depois de ultrapassar a marca "algumas centenas de bytes", é hora de usar os mallocamigos. Você ainda pode ter falhas de alocação, mas pelo menos terá alguma indicação da falha, em vez de apenas estourar a pilha.


35
Portanto, não há realmente nenhum problema com isso que você também não teria ao declarar matrizes grandes?
TED

88
@ Sean: Sim, o risco de estouro de pilha é o motivo, mas esse é um pouco tolo. Primeiro, porque (como Vaibhav diz) grandes matrizes locais causam exatamente o mesmo problema, mas não são tão difamadas. Além disso, a recursão pode facilmente explodir a pilha. Desculpe, mas estou pedindo a você que esperançosamente contrarie a idéia predominante de que o motivo indicado na página de manual seja justificado.
Jrandom_hacker

49
Meu argumento é que a justificativa dada na página de manual não faz sentido, pois alloca () é exatamente tão "ruim" quanto as outras coisas (matrizes locais ou funções recursivas) que são consideradas kosher.
Jrandom_hacker

39
@ninjalj: Não por programadores C / C ++ altamente experientes, mas acho que muitas pessoas que temem alloca()não têm o mesmo medo de matrizes ou recursões locais (de fato, muitas pessoas que gritarão alloca()elogiarão a recursão porque "parece elegante") . Eu concordo com o conselho de Shaun ("alloca () é bom para pequenas alocações"), mas eu discordo da mentalidade de enquadrar alloca () como exclusivamente ruim entre os 3 - eles são igualmente perigosos!
Jrandom_hacker

35
Nota: Dada a estratégia "otimista" de alocação de memória do Linux, você provavelmente não receberá nenhuma indicação de uma falha de exaustão de heap ... em vez disso, malloc () retornará um bom ponteiro não NULL e, quando você tentar realmente acessar o espaço de endereço para o qual ele aponta, seu processo (ou algum outro processo, imprevisivelmente) será morto pelo OOM-killer. É claro que esse é um "recurso" do Linux, e não um problema de C / C ++ em si, mas é algo a ter em mente ao debater se alloca () ou malloc () é "mais seguro". :)
Jeremy Friesner

209

Um dos bugs mais memoráveis ​​que tive foi o uso de uma função inline usada alloca. Ele se manifestou como um estouro de pilha (porque é alocado na pilha) em pontos aleatórios da execução do programa.

No arquivo de cabeçalho:

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

No arquivo de implementação:

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

Então, o que aconteceu foi a DoSomethingfunção incorporada do compilador e todas as alocações de pilha estavam acontecendo dentro da Process()função e, assim, explodindo a pilha. Em minha defesa (e não fui eu quem encontrou o problema; tive que chorar para um dos desenvolvedores seniores quando não consegui consertá-lo), não foi direto alloca, foi um dos conversores de string ATL macros.

Portanto, a lição é - não use allocaem funções que você acha que podem estar embutidas.


91
Interessante. Mas isso não se qualificaria como um bug do compilador? Afinal, o inlining mudou o comportamento do código (atrasou a liberação do espaço alocado usando alloca).
sleske

60
Aparentemente, pelo menos o GCC levará isso em consideração: "Observe que certos usos em uma definição de função podem torná-lo inadequado para substituição em linha. Entre esses usos estão: uso de varargs, uso de alloca, [...]". gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske

137
Qual compilador você estava fumando?
Thomas Eding

22
O que não entendo é por que o compilador não faz bom uso do escopo para determinar que as alocas no sub-escopo são mais ou menos "liberadas": o ponteiro da pilha pode voltar ao seu ponto antes de entrar no escopo, como é feito quando retornando de função (que não poderia?)
Moala

7
Fiz uma votação baixa, mas a resposta está bem escrita: concordo com os outros que você está culpando o alloca pelo que é claramente um bug do compilador . O compilador fez uma suposição incorreta em uma otimização que não deveria ter feito. Trabalhar em torno de um bug do compilador é bom, mas eu não culpo nada por isso, exceto o compilador.
Evan Carroll

75

Pergunta antiga, mas ninguém mencionou que deveria ser substituída por matrizes de comprimento variável.

char arr[size];

ao invés de

char *arr=alloca(size);

Está no padrão C99 e existia como extensão de compilador em muitos compiladores.


5
É mencionado por Jonathan Leffler em um comentário à resposta de Arthur Ulfeldt.
Ninjalj

2
De fato, mas também mostra como é fácil perder, como eu não tinha visto, apesar de ler todas as respostas antes de postar.
Patrick Schlüter

6
Uma observação - essas são matrizes de comprimento variável, não matrizes dinâmicas. Os últimos são redimensionáveis ​​e geralmente implementados no heap.
Tim Čas 9/12/12

11
O Visual Studio 2015 compilando alguns C ++ tem o mesmo problema.
ahcox

2
Linus Torvalds não gosta de VLAs no kernel do Linux . A partir da versão 4.20, o Linux deveria estar quase livre de VLA.
Cristian Ciupitu 10/01/19

60

alloca () é muito útil se você não puder usar uma variável local padrão porque seu tamanho precisaria ser determinado em tempo de execução e você pode garantir absolutamente que o ponteiro obtido de alloca () NUNCA será usado após o retorno dessa função .

Você pode estar razoavelmente seguro se você

  • não retorne o ponteiro ou qualquer coisa que o contenha.
  • não armazene o ponteiro em nenhuma estrutura alocada no heap
  • não deixe nenhum outro thread usar o ponteiro

O perigo real vem da chance de alguém violar essas condições algum tempo depois. Com isso em mente, é ótimo para passar buffers para funções que formatam texto neles :)


12
O recurso VLA (array de comprimento variável) do C99 suporta variáveis ​​locais de tamanho dinâmico, sem exigir explicitamente o uso de alloca ().
22611 Jonathan Leffler

2
neato! encontrados mais informações na seção '3.4 comprimento variável de matriz' de programmersheaven.com/2/Pointers-and-Arrays-page-2
Arthur Ulfeldt

11
Mas isso não é diferente de lidar com ponteiros para variáveis ​​locais. Eles podem ser enganados com ...
glglgl

2
@ Jonathan Leffler Uma coisa que você pode fazer com o alloca, mas não com o VLA, é usar palavras-chave restritas. Assim: float * restringir fortemente_utilizado = alloca (sizeof (float) * size); em vez de flutuar heavy_used_arr [size]. Isso pode ajudar alguns compiladores (gcc 4.8 no meu caso) a otimizar o assembly, mesmo que o tamanho seja uma constante de compilação. Veja a minha pergunta sobre isso: stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz

@JonathanLeffler Um VLA é local para o bloco que o contém. Por outro lado, alloca()aloca memória que dura até o final da função. Isso significa que parece não haver tradução direta e conveniente para o VLA de f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }. Se você acha que é possível converter automaticamente usos de allocapara usos de VLA, mas exige mais de um comentário para descrever como, eu posso fazer disso uma pergunta.
Pascal Cuoq

40

Conforme observado nesta postagem do grupo de notícias , existem alguns motivos pelos quais o uso allocapode ser considerado difícil e perigoso:

  • Nem todos os compiladores suportam alloca.
  • Alguns compiladores interpretam o comportamento pretendido de maneira allocadiferente, portanto, a portabilidade não é garantida, mesmo entre os compiladores que o suportam.
  • Algumas implementações são com erros.

24
Uma coisa que vi mencionada nesse link que não está em nenhum outro lugar nesta página é que uma função que usa alloca()requer registros separados para manter o ponteiro de pilha e o ponteiro de quadro. Em CPUs x86> = 386, o ponteiro da pilha ESPpode ser usado para ambos, liberando EBP- a menos que alloca()seja usado.
Jrandom_hacker

10
Outro ponto positivo nessa página é que, a menos que o gerador de código do compilador o manipule como um caso especial, f(42, alloca(10), 43);poderá travar devido à possibilidade de o ponteiro da pilha ser ajustado alloca() depois que pelo menos um dos argumentos for pressionado.
Jrandom_hacker

3
O post vinculado parece ter sido escrito por John Levine - o cara que escreveu "Linkers and Loaders", eu confiaria no que ele dissesse.
user318904

3
A postagem vinculada é uma resposta a uma postagem de John Levine.
A.Wilcox

6
Tenha em mente que muita coisa mudou desde 1991. Todos os compiladores C modernos (mesmo em 2009) precisam lidar com alloca como um caso especial; é uma função intrínseca e não comum, e pode nem chamar uma função. Portanto, a questão da aloca-em-parâmetro (que surgiu na K&R C a partir da década de 1970) não deveria ser um problema agora. Mais detalhes em um comentário que fiz em resposta Tony D's
Greggo

26

Uma questão é que não é padrão, embora seja amplamente suportado. Sendo outras coisas iguais, eu sempre usaria uma função padrão em vez de uma extensão comum do compilador.


21

Ainda assim, o uso de cocaína é desencorajado, por quê?

Não percebo tal consenso. Muitos profissionais fortes; alguns contras:

  • O C99 fornece matrizes de comprimento variável, que costumam ser usadas preferencialmente, pois a notação é mais consistente com matrizes de comprimento fixo e intuitiva em geral.
  • muitos sistemas têm menos espaço de memória / endereço geral disponível para a pilha do que para a pilha, o que torna o programa um pouco mais suscetível à exaustão de memória (devido ao estouro de pilha): isso pode ser visto como algo bom ou ruim - um Uma das razões pelas quais a pilha não cresce automaticamente da mesma forma que a pilha é para impedir que programas fora de controle tenham tanto impacto adverso em toda a máquina
  • quando usada em um escopo mais local (como a whileou forloop) ou em vários escopos, a memória se acumula por iteração / escopo e não é liberada até que a função saia: isso contrasta com as variáveis ​​normais definidas no escopo de uma estrutura de controle (por exemplo, for {int i = 0; i < 2; ++i) { X }acumularia a allocamemória solicitada no X, mas a memória para uma matriz de tamanho fixo seria reciclada por iteração).
  • compiladores modernos normalmente não inlinefuncionam com a chamada alloca, mas se você forçá-los, allocaisso acontecerá no contexto dos chamadores (ou seja, a pilha não será liberada até que o chamador retorne)
  • há muito tempo, allocamigrou de um recurso / hack não portátil para uma extensão padronizada, mas alguma percepção negativa pode persistir
  • o tempo de vida está vinculado ao escopo da função, que pode ou não se adequar melhor ao programador do que malloco controle explícito
  • o uso mallocincentiva o pensamento sobre a desalocação - se isso é gerenciado por meio de uma função de wrapper (por exemplo WonderfulObject_DestructorFree(ptr)), a função fornece um ponto para a implementação de operações de limpeza (como fechar descritores de arquivo, liberar ponteiros internos ou fazer log) sem alterações explícitas no cliente código: às vezes é um bom modelo para adotar de forma consistente
    • nesse estilo de programação pseudo-OO, é natural querer algo como WonderfulObject* p = WonderfulObject_AllocConstructor();- isso é possível quando o "construtor" é uma função que retorna mallocmemória (como a memória permanece alocada após a função retornar o valor a ser armazenado p), mas não se o "construtor" usaralloca
      • uma versão macro WonderfulObject_AllocConstructorpoderia conseguir isso, mas "as macros são más", pois podem entrar em conflito entre si e com o código não macro e criar substituições não intencionais e consequentes problemas difíceis de diagnosticar
    • as freeoperações ausentes podem ser detectadas pelo ValGrind, Purify etc., mas as chamadas "destruidoras" ausentes nem sempre podem ser detectadas - um benefício muito tênue em termos de aplicação do uso pretendido; algumas alloca()implementações (como as GCCs) usam uma macro embutida para alloca(), portanto, a substituição em tempo de execução de uma biblioteca de diagnóstico de uso de memória não é possível do jeito que é para malloc/ realloc/ free(por exemplo, cerca elétrica)
  • algumas implementações têm problemas sutis: por exemplo, na página de manual do Linux:

    Em muitos sistemas, o alloca () não pode ser usado na lista de argumentos de uma chamada de função, porque o espaço da pilha reservado por alloca () apareceria na pilha no meio do espaço para os argumentos da função.


Eu sei que essa pergunta está marcada como C, mas como programador de C ++, pensei em usar o C ++ para ilustrar a utilidade potencial de alloca: o código abaixo (e aqui na ideone ) cria um vetor que rastreia tipos polimórficos de tamanhos diferentes e com alocação de pilha (com tempo de vida vinculado ao retorno da função) em vez do heap alocado.

#include <alloca.h>
#include <iostream>
#include <vector>

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<Base*> 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<Base*>::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
    }
}

não +1 por causa da maneira idiossincrática suspeita de lidar com vários tipos :-(
einpoklum 17/07/2015

@einpoklum: bem, isso é profundamente esclarecedor ... obrigado.
Tony Delroy

11
Deixe-me reformular: Esta é uma resposta muito boa. Até o ponto em que acho que você está sugerindo que as pessoas usem uma espécie de contra-padrão.
einpoklum

O comentário da página de manual do linux é muito antigo e, tenho certeza, obsoleto. Todos os compiladores modernos sabem o que é alloca () e não tropeçam nos cadarços dessa maneira. No K&R C antigo, (1) todas as funções usavam ponteiros de quadro (2) Todas as chamadas de funções eram {push args on stack} {call func} {add # n, sp}. alloca era uma função lib que aumentaria o empilhamento, o compilador nem sabia disso. (1) e (2) não são mais verdadeiros, portanto, a alloca não pode funcionar dessa maneira (agora é intrínseca). No antigo C, chamar alloca no meio de pressionar args obviamente quebraria essas suposições também.
Greggo #

4
Em relação ao exemplo, eu estaria geralmente preocupados com algo que requer always_inline à corrupção de memória evitar ....
Greggo

14

Todas as outras respostas estão corretas. No entanto, se o que você deseja alocar alloca()é razoavelmente pequeno, acho que é uma boa técnica mais rápida e conveniente do que usar malloc()ou não.

Em outras palavras, alloca( 0x00ffffff )é perigoso e provavelmente causa estouro, exatamente o que char hugeArray[ 0x00ffffff ];é. Seja cauteloso e razoável e você ficará bem.


12

Muitas respostas interessantes para essa pergunta "antiga", até algumas respostas relativamente novas, mas não encontrei nenhuma que mencionasse isso ...

Quando usado de maneira adequada e cuidadosa, o uso consistente alloca() (talvez em todo o aplicativo) para lidar com pequenas alocações de tamanho variável (ou C99 VLAs, quando disponíveis) pode levar a um crescimento geral menor da pilha do que uma implementação equivalente usando matrizes locais de tamanho fixo de tamanho fixo . Portanto, alloca()pode ser bom para sua pilha se você a usar com cuidado.

Encontrei essa citação em ... OK, fiz essa citação. Mas realmente, pense nisso ....

@j_random_hacker está muito certo em seus comentários sob outras respostas: Evitar o uso de a alloca()favor de matrizes locais superdimensionadas não torna seu programa mais seguro contra estouros de pilha (a menos que seu compilador tenha idade suficiente para permitir a inclusão de funções usadas alloca()nesse caso, você deve atualização, ou a menos que você use alloca()loops internos; nesse caso, você não deve ... usar alloca()loops internos).

Trabalhei em ambientes de desktop / servidor e sistemas embarcados. Muitos sistemas embarcados nem sequer usam um heap (eles nem se vinculam no suporte a ele), por motivos que incluem a percepção de que a memória alocada dinamicamente é ruim devido aos riscos de vazamento de memória em um aplicativo que nunca sempre reinicia por anos, ou a justificativa mais razoável de que a memória dinâmica é perigosa porque não se pode ter certeza de que um aplicativo nunca fragmentará sua pilha ao ponto de falsa exaustão de memória. Portanto, os programadores incorporados ficam com poucas alternativas.

alloca() (ou VLAs) pode ser a ferramenta certa para o trabalho.

Eu vi várias vezes em que um programador cria um buffer alocado para a pilha "grande o suficiente para lidar com qualquer caso possível". Em uma árvore de chamada profundamente aninhada, o uso repetido desse padrão (anti -?) Leva ao uso exagerado da pilha. (Imagine uma árvore de chamadas com 20 níveis de profundidade, onde, em cada nível, por diferentes razões, a função superaloca cegamente um buffer de 1024 bytes "apenas por segurança", quando geralmente usa apenas 16 ou menos deles, e apenas em casos raros podem usar mais.) Uma alternativa é usaralloca()ou VLAs e aloque apenas o espaço de pilha necessário para sua função, para evitar sobrecarregar desnecessariamente a pilha. Felizmente, quando uma função na árvore de chamadas precisa de uma alocação maior que o normal, outras ainda estão usando suas alocações pequenas normais e o uso geral da pilha de aplicativos é significativamente menor do que se todas as funções superalocassem cegamente um buffer local .

Mas se você optar por usar alloca()...

Com base em outras respostas nesta página, parece que os VLAs devem ser seguros (eles não compõem alocações de pilha se chamados de dentro de um loop), mas se você estiver usando alloca(), tenha cuidado para não usá-lo dentro de um loop e faça verifique se sua função não pode ser incorporada se houver alguma chance de ser chamada no loop de outra função.


Eu concordo com este ponto. O perigoso de alloca()é verdade, mas pode-se dizer o mesmo dos vazamentos de memória malloc()(por que não usar um GC então? Alguém poderia argumentar). alloca()quando usado com cuidado pode ser realmente útil para diminuir o tamanho da pilha.
Felipe Tonello 02/02

Outro bom motivo para não usar a memória dinâmica, especialmente no incorporado: é mais complicado do que ficar na pilha. O uso de memória dinâmica requer procedimentos especiais e estruturas de dados, enquanto na pilha é (para simplificar) uma questão de adicionar / subtrair um número maior do stackpointer.
tehftw

Nota: O exemplo "usando um buffer fixo [MAX_SIZE]" destaca por que a política de confirmação excessiva de memória funciona tão bem. Os programas alocam memória que eles nunca podem tocar, exceto nos limites do tamanho do buffer. Portanto, é bom que o Linux (e outros SOs) não atribua uma página de memória até que seja usada pela primeira vez (em oposição a malloc'd). Se o buffer for maior que uma página, o programa poderá usar apenas a primeira página e não desperdiçar o restante da memória física.
Katastic Voyage 23/06/19

@KatasticVoyage A menos que MAX_SIZE seja maior que (ou pelo menos igual a) o tamanho do tamanho da página de memória virtual do sistema, seu argumento não será válido. Também em sistemas embarcados sem memória virtual (muitos MCUs incorporados não possuem MMU), a política de memória de supercomprometimento pode ser boa do ponto de vista de "garantir que seu programa seja executado em todas as situações", mas essa garantia vem com o preço que o tamanho da sua pilha também deve ser alocado para oferecer suporte a essa política de memória de supercomprometimento. Em alguns sistemas embarcados, esse é um preço que alguns fabricantes de produtos de baixo custo não estão dispostos a pagar.
phonetagger 5/09/19

11

Todo mundo já apontou a grande coisa que é o comportamento indefinido potencial de um estouro de pilha, mas devo mencionar que o ambiente Windows possui um ótimo mecanismo para capturar isso usando exceções estruturadas (SEH) e páginas de proteção. Como a pilha cresce apenas conforme necessário, essas páginas de proteção residem em áreas não alocadas. Se você alocá-los (excedendo a pilha), uma exceção será lançada.

Você pode capturar essa exceção SEH e chamar _resetstkoflw para redefinir a pilha e continuar no seu caminho alegre. Não é o ideal, mas é outro mecanismo para pelo menos saber que algo deu errado quando o material atinge o ventilador. * Nix pode ter algo semelhante que eu não conheço.

Eu recomendo limitar seu tamanho máximo de alocação, envolvendo alloca e rastreando-o internamente. Se você fosse realmente sincero, jogaria algumas sentinelas de escopo na parte superior da sua função para rastrear alocações de aloca no escopo e na sanidade da função e verifique isso com o valor máximo permitido para o seu projeto.

Além disso, além de não permitir vazamentos de memória, o alloca não causa fragmentação de memória, o que é muito importante. Não acho que alloca seja uma má prática se você a usar de maneira inteligente, o que é basicamente verdadeiro para tudo. :-)


O problema é que isso alloca()pode exigir tanto espaço que o stackpointer cai no monte. Com isso, um invasor que pode controlar o tamanho alloca()e os dados que entram nesse buffer podem substituir o heap (o que é muito ruim).
12431234123412341234123

SEH é apenas uma coisa do Windows. Isso é ótimo se você se importa apenas com o código em execução no Windows, mas se ele precisar ser multiplataforma (ou se você estiver escrevendo um código que só é executado em uma plataforma que não seja Windows), não poderá confiar em ter SEH.
George

10

alloca () é agradável e eficiente ... mas também está profundamente quebrado.

  • comportamento do escopo quebrado (escopo da função em vez do escopo do bloco)
  • use inconsistente com malloc (o ponteiro com aloca () não deve ser liberado; daí em diante você deve rastrear de onde os ponteiros estão vindo para liberar () somente aqueles que você obteve com malloc () )
  • mau comportamento quando você também usa inlining (o escopo às vezes vai para a função de chamada, dependendo se o receptor está embutido ou não).
  • sem verificação do limite da pilha
  • comportamento indefinido em caso de falha (não retorna NULL como malloc ... e o que significa falha porque não verifica os limites da pilha de qualquer maneira ...)
  • não padrão ansi

Na maioria dos casos, você pode substituí-lo usando variáveis ​​locais e tamanho majorante. Se for usado para objetos grandes, colocá-los na pilha geralmente é uma idéia mais segura.

Se você realmente precisa do C, pode usar o VLA (sem vla em C ++, muito ruim). Eles são muito melhores que alloca () em relação ao comportamento e consistência do escopo. A meu ver, o VLA é uma espécie de alloca () corrigida .

É claro que uma estrutura ou matriz local usando um majorante do espaço necessário ainda é melhor, e se você não tiver essa alocação de heap majorante usando malloc () simples, provavelmente é sensato. Não vejo nenhum caso de uso sensato em que você realmente precise de alloca () ou VLA.


Eu não vejo a razão para a downvote (sem qualquer comentário, por sinal)
GD1

Somente nomes têm escopo. allocanão cria um nome, apenas um intervalo de memória que tem vida útil .
Curiousguy

@curiousguy: você está apenas brincando com as palavras. Para variáveis ​​automáticas, eu poderia falar da vida útil da memória subjacente, pois ela corresponde ao escopo do nome. De qualquer forma, o problema não é como o chamamos, mas a instabilidade da vida / escopo da memória retornada pela alloca e o comportamento excepcional.
kriss

2
Eu gostaria que alloca tivesse uma "freea" correspondente, com uma especificação que chamar "freea" desfizesse os efeitos da "alloca" que criou o objeto e todos os subsequentes, e um requisito de que o armazenamento 'alocado' dentro de uma função deve ser 'libertado' dentro dele também. Isso tornaria possível que quase todas as implementações suportassem alloca / freea de maneira compatível, aliviasse os problemas subjacentes e geralmente tornasse as coisas muito mais limpas.
supercat

2
@ Supercat - Eu também desejo. Por essa razão (e mais), eu uso uma camada de abstração (principalmente macros e funções inline) para que eu não nunca chamar allocaou mallocou freediretamente. Eu digo coisas como {stack|heap}_alloc_{bytes,items,struct,varstruct}e {stack|heap}_dealloc. Então, heap_deallocapenas chama freee stack_deallocé um não-op. Dessa forma, as alocações de pilha podem ser facilmente rebaixadas para alocações de heap, e as intenções também são mais claras.
Todd Lehman

9

Aqui está o porquê:

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

Não que alguém escreva esse código, mas o argumento de tamanho para o qual você está passando allocaquase certamente vem de algum tipo de entrada, que poderia ter como objetivo malicioso levar seu programa a allocaalgo enorme como esse. Afinal, se o tamanho não é baseado na entrada ou não tem a possibilidade de ser grande, por que você simplesmente não declarou um buffer local pequeno e de tamanho fixo?

Praticamente todo o código que usa allocae / ou o C99 vlas possui bugs sérios que levarão a falhas (se você tiver sorte) ou comprometimento de privilégios (se você não tiver tanta sorte).


11
O mundo talvez nunca saiba. :( Dito isso, espero que você possa esclarecer uma pergunta que tenho alloca. Você disse que quase todo código que o utiliza tem um bug, mas eu estava pensando em usá-lo; eu normalmente ignoraria essa afirmação, mas chegaria Estou escrevendo uma máquina virtual e gostaria de alocar variáveis ​​que não escapam da função na pilha, em vez de dinamicamente, devido à enorme aceleração. Existe uma alternativa ? abordagem que tem as mesmas características de desempenho Eu sei que posso chegar perto com pools de memória, mas que ainda não é tão barato que você faria.?
GManNickG

7
Sabe o que também é perigoso? Isso: *0 = 9;INCRÍVEL !!! Eu acho que nunca devo usar ponteiros (ou pelo menos desreferenciá-los). Err, espera. Eu posso testar para ver se é nulo. Hmm. Acho que também posso testar o tamanho da memória que quero alocar via alloca. Homem esquisito. Esquisito.
Thomas Eding

7
*0=9;não é válido C. Quanto a testar o tamanho para o qual você passa alloca, teste contra o quê? Não há como saber o limite e, se você for testá-lo em um pequeno tamanho seguro conhecido (por exemplo, 8k), use uma matriz de tamanho fixo na pilha.
R .. GitHub Pare de ajudar o gelo

7
O problema com o argumento "ou o tamanho é conhecido por ser pequeno o suficiente ou depende da entrada e, portanto, pode ser arbitrariamente grande", como eu vejo, é que ele se aplica tão fortemente à recursão. Um compromisso prático (para os dois casos) é assumir que, se o tamanho for limitado small_constant * log(user_input), provavelmente teremos memória suficiente.
Jrandom_hacker

11
De fato, você identificou o ONE caso em que o VLA / alloca é útil: algoritmos recursivos em que o espaço máximo necessário em qualquer quadro de chamada pode ser tão grande quanto N, mas onde a soma do espaço necessário em todos os níveis de recursão é N ou alguma função de N que não cresce rapidamente.
R .. GitHub Pare de ajudar o gelo

9

Não acho que alguém tenha mencionado isso: o uso de alloca em uma função dificultará ou desabilitará algumas otimizações que poderiam ser aplicadas na função, pois o compilador não pode saber o tamanho do quadro de pilha da função.

Por exemplo, uma otimização comum dos compiladores C é eliminar o uso do ponteiro de quadro dentro de uma função; acessos ao quadro são feitos em relação ao ponteiro da pilha; então há mais um registro para uso geral. Mas se alloca for chamado dentro da função, a diferença entre sp e fp será desconhecida para parte da função, portanto, essa otimização não pode ser feita.

Dada a raridade de seu uso e seu status obscuro como uma função padrão, os projetistas de compiladores possivelmente desativam qualquer otimização que possa causar problemas com o alloca, se for necessário mais do que um pequeno esforço para fazê-lo funcionar com o alloca.

ATUALIZAÇÃO: Como matrizes locais de tamanho variável foram adicionadas a C e, como apresentam aloca problemas de geração de código muito semelhantes aos do alloca, vejo que 'raridade de uso e status obscuro' não se aplica ao mecanismo subjacente; mas eu ainda suspeitaria que o uso de alloca ou VLA tende a comprometer a geração de código dentro de uma função que os use. Gostaria de receber qualquer feedback dos designers do compilador.


11
Matrizes de comprimento variável nunca foram adicionadas ao C ++.
Nir Friedman

@NirFriedman Indeed. Eu acho que havia uma lista de recursos da Wikipedia baseada em uma proposta antiga.
Greggo

> Eu ainda suspeitaria que o uso de alloca ou VLA tende a comprometer a geração de código. Eu pensaria que o uso de alloca requer um ponteiro de quadro, porque o ponteiro de pilha se move de maneiras que não são óbvias no momento da compilação. alloca pode ser chamado em um loop para manter mais memória da pilha, ou com um tamanho calculado em tempo de execução, etc. Se houver um ponteiro de quadro, o código gerado terá uma referência estável aos locais e o ponteiro da pilha poderá fazer o que quiser; não é usado.
Kaz

8

Uma armadilha allocaé que a longjmprebobina.

Ou seja, se você salvar um contexto com setjmp, em seguida, allocaum pouco de memória, em seguida, longjmppara o contexto, você pode perder a allocamemória. O ponteiro da pilha está de volta onde estava e, portanto, a memória não é mais reservada; se você chamar uma função ou executar outra alloca, você derrotará o original alloca.

Para esclarecer, o que estou me referindo especificamente aqui é uma situação em longjmpque não retorna da função em que allocaocorreu! Em vez disso, uma função salva o contexto com setjmp; então aloca memória com allocae, finalmente, um longjmp ocorre nesse contexto. A allocamemória dessa função não é toda liberada; apenas toda a memória que alocou desde o setjmp. Claro, estou falando de um comportamento observado; nenhum requisito desse tipo está documentado sobre algum allocaque eu conheça.

O foco na documentação geralmente está no conceito de que a allocamemória está associada a uma ativação de função , não a qualquer bloco; que várias invocações allocasimplesmente capturam mais memória da pilha, que é liberada quando a função termina. Não tão; a memória está realmente associada ao contexto do procedimento. Quando o contexto é restaurado com longjmp, o mesmo ocorre com o allocaestado anterior . É uma conseqüência do próprio registro de ponteiro de pilha ser usado para alocação e também (necessariamente) salvo e restaurado no arquivo jmp_buf.

Aliás, isso, se funcionar dessa maneira, fornece um mecanismo plausível para deliberadamente liberar memória que foi alocada com alloca.

Eu corri para isso como a causa raiz de um bug.


11
Porém, é isso que ele deve fazer - longjmpretorna e faz com que o programa esqueça tudo o que aconteceu na pilha: todas as variáveis, chamadas de função etc. E allocaé como uma matriz na pilha, portanto, espera-se que sejam derrotadas como tudo na pilha.
tehftw

11
man allocadeu a seguinte frase: "Como o espaço alocado por alloca () é alocado no quadro da pilha, esse espaço é liberado automaticamente se o retorno da função for saltado por uma chamada para longjmp (3) ou siglongjmp (3).". Portanto, está documentado que a memória alocada com allocaé derrotada após a longjmp.
tehftw

@tehftw A situação descrita ocorre sem que um retorno de função seja ignorado longjmp. A função de destino ainda não retornou. Já fez setjmp, allocae então longjmp. O longjmppode rebobinar o allocaestado de volta ao que era naquele setjmptempo. Ou seja, o ponteiro movido por allocasofre do mesmo problema que uma variável local que não foi marcada volatile!
Kaz

3
Não entendo por que isso seria inesperado. Quando você , setjmpentão alloca, e então longjmp, é normal que allocafosse rebobinado. O objetivo de tudo longjmpé voltar ao estado em que foi salvo setjmp!
tehftw

@tehftw Eu nunca vi essa interação específica documentada. Portanto, não pode ser invocado de nenhuma maneira, a não ser pela investigação empírica com compiladores.
Kaz

7

Um local onde alloca()é especialmente perigoso do que malloc()o kernel - o kernel de um sistema operacional típico possui um espaço de pilha de tamanho fixo codificado em um de seus cabeçalhos; não é tão flexível quanto a pilha de um aplicativo. Fazer uma chamada para alloca()um tamanho não autorizado pode causar uma falha no kernel. Certos compiladores avisam o uso alloca()(e até mesmo de VLAs) sob certas opções que devem ser ativadas durante a compilação de um código do kernel - aqui, é melhor alocar memória no heap que não é corrigido por um limite codificado.


7
alloca()não é mais perigoso do que int foo[bar];onde barestá um número inteiro arbitrário.
Todd Lehman

@ToddLehman Está correto, e por esse exato motivo, banimos os VLAs no kernel por vários anos e estamos livres de VLA desde 2018 :-)
Chris Down

6

Se você acidentalmente gravar além do bloco alocado com alloca(devido a um estouro de buffer, por exemplo), substituirá o endereço de retorno da sua função, porque esse está localizado "acima" na pilha, ou seja, após o bloco alocado.

_alloca bloco na pilha

A consequência disso é dupla:

  1. O programa falhará espetacularmente e será impossível dizer por que ou onde ele falhou (a pilha provavelmente se descontrairá em um endereço aleatório devido ao ponteiro do quadro substituído).

  2. Isso torna o buffer overflow muitas vezes mais perigoso, pois um usuário mal-intencionado pode criar uma carga útil especial que seria colocada na pilha e, portanto, acabaria sendo executada.

Por outro lado, se você escrever além de um bloco na pilha, "apenas" terá corrupção na pilha. O programa provavelmente será encerrado inesperadamente, mas irá desenrolar a pilha corretamente, reduzindo assim a chance de execução de código malicioso.


11
Nada nessa situação é dramaticamente diferente dos perigos de sobrecarregar o buffer de um buffer alocado por pilha de tamanho fixo. Este perigo não é exclusivo alloca.
phonetagger 31/03

2
Claro que não. Mas verifique a pergunta original. A questão é: qual é o perigo allocaem comparação com malloc(portanto, não um buffer de tamanho fixo na pilha).
Rustyx

Ponto menor, mas as pilhas em alguns sistemas crescem para cima (por exemplo, microprocessadores PIC de 16 bits).
EBlake 5/03

5

Infelizmente, o verdadeiramente impressionante alloca()está faltando no tcc quase incrível. Gcc tem alloca().

  1. Semeia a semente de sua própria destruição. Com retorno como destruidor.

  2. Como se malloc()ele retornasse um ponteiro inválido em caso de falha, que irá falhar nos sistemas modernos com uma MMU (e esperamos reiniciar aqueles sem).

  3. Diferente das variáveis ​​automáticas, você pode especificar o tamanho no tempo de execução.

Funciona bem com recursão. Você pode usar variáveis ​​estáticas para obter algo semelhante à recursão de cauda e usar apenas algumas outras informações de passagem para cada iteração.

Se você for muito fundo, terá a garantia de um segfault (se você tiver uma MMU).

Observe que ele malloc()não oferece mais, pois retorna NULL (que também será segfault se atribuído) quando o sistema estiver sem memória. Ou seja, tudo o que você pode fazer é pagar a fiança ou apenas tentar atribuí-lo de qualquer maneira.

Para usar malloc(), uso globals e atribuo a eles NULL. Se o ponteiro não for NULL, libero-o antes de usá-lo malloc().

Você também pode usar realloc()como caso geral se desejar copiar dados existentes. Você precisa verificar o ponteiro antes para descobrir se deseja copiar ou concatenar após o realloc().

3.2.5.2 Vantagens da alloca


4
Na verdade, a especificação alloca não diz que retorna um ponteiro inválido em caso de falha (estouro de pilha) diz ter comportamento indefinido ... e para malloc diz que retorna NULL, não um ponteiro inválido aleatório (OK, a implementação otimizada da memória do Linux faz com que sem utilidade).
kriss

@kriss Linux pode matar o seu processo, mas pelo menos ele não se aventurar em um comportamento indefinido
craig65535

@ craig65535: a expressão comportamento indefinido geralmente significa que esse comportamento não é definido pelas especificações C ou C ++. De maneira alguma, será aleatório ou instável em qualquer SO ou compilador. Portanto, não faz sentido associar UB ao nome de um sistema operacional como "Linux" ou "Windows". Não tem nada a ver com isso.
quer

Eu estava tentando dizer que malloc retornando NULL, ou, no caso do Linux, um acesso à memória que mata seu processo, é preferível ao comportamento indefinido da alloca. Acho que devo ter interpretado mal o seu primeiro comentário.
precisa saber é o seguinte

3

Os processos têm apenas uma quantidade limitada de espaço na pilha disponível - muito menos do que a quantidade de memória disponível malloc().

Ao usá- alloca()lo, você aumenta drasticamente suas chances de obter um erro de Estouro de pilha (se tiver sorte ou uma falha inexplicável, se não tiver).


Isso depende muito da aplicação. Não é incomum que um aplicativo incorporado com memória limitada tenha um tamanho de pilha maior que o heap (se houver um heap).
EBlake 4/03

3

Não é muito bonito, mas se o desempenho realmente importa, você pode pré-alocar algum espaço na pilha.

Se você já tem o tamanho máximo da memória bloqueado e precisa manter verificações de excesso, faça algo como:

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

    (...)
}

12
É garantido que a matriz de caracteres esteja alinhada corretamente para qualquer tipo de dados? alloca oferece tal promessa.
Juho Östman 17/09/10

@ JuhoÖstman: você pode usar uma matriz de struct (ou de qualquer tipo) em vez de char, se tiver problemas de alinhamento.
kriss

Isso é chamado de matriz de comprimento variável . É suportado em C90 e acima, mas não em C ++. Consulte Posso usar uma matriz de comprimento variável C em C ++ 03 e C ++ 11?
jww 27/07

3

A função alloca é ótima e todos os opositores estão simplesmente espalhando FUD.

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

Matriz e parray são EXATAMENTE iguais, com EXATAMENTE os mesmos riscos. Dizer que um é melhor que o outro é uma escolha sintática, não técnica.

Quanto à escolha de variáveis ​​de pilha versus variáveis ​​de heap, há muitas vantagens em programas de longa execução usando pilha sobre heap para variáveis ​​com vida útil no escopo. Você evita a fragmentação de heap e pode aumentar o espaço do processo com espaço de heap não utilizado (inutilizável). Você não precisa limpá-lo. Você pode controlar a alocação de pilha no processo.

Por que isso é ruim?


3

Na verdade, não é garantido que o alloca use a pilha. De fato, a implementação do alloca do gcc-2.95 aloca memória do heap usando o próprio malloc. Além disso, essa implementação é incorreta, pode levar a um vazamento de memória e a um comportamento inesperado se você a chamar dentro de um bloco com um uso adicional de goto. Não, para dizer que você nunca deve usá-lo, mas algumas vezes alloca leva a mais sobrecarga do que libera.


Parece que o gcc-2.95 quebrou o alloca e provavelmente não pode ser usado com segurança para programas que exigem alloca. Como ele teria limpado a memória quando longjmpé usado para abandonar os quadros que funcionavam alloca? Quando alguém usaria o gcc 2.95 hoje?
Kaz

2

IMHO, alloca é considerado uma má prática, porque todo mundo tem medo de esgotar o limite de tamanho da pilha.

Eu aprendi muito lendo este tópico e alguns outros links:

Eu uso o alloca principalmente para tornar meus arquivos C simples compiláveis ​​no msvc e gcc sem nenhuma alteração, estilo C89, sem #ifdef _MSC_VER, etc.

Obrigado ! Este tópico me fez inscrever neste site :)


Lembre-se de que não existe um "encadeamento" neste site. O estouro de pilha tem um formato de perguntas e respostas, não um formato de encadeamento de discussão. "Resposta" não é como "Responder" em um fórum de discussão; significa que você está realmente fornecendo uma resposta para a pergunta e não deve ser usado para responder a outras respostas ou comentar sobre o tópico. Depois de ter pelo menos 50 representantes, você pode postar comentários , mas não deixe de ler a seção "Quando não devo comentar?" seção. Por favor, leia a página Sobre para entender melhor o formato do site.
Adi Inbar

1

Na minha opinião, alloca (), quando disponível, deve ser usado apenas de maneira restrita. Muito parecido com o uso de "goto", um grande número de pessoas razoáveis ​​tem forte aversão não apenas ao uso de, mas também à existência de alloca ().

Para uso incorporado, onde o tamanho da pilha é conhecido e os limites podem ser impostos por convenção e análise sobre o tamanho da alocação e onde o compilador não pode ser atualizado para oferecer suporte ao C99 +, o uso de alloca () é bom, e eu estive conhecido por usá-lo.

Quando disponíveis, os VLAs podem ter algumas vantagens sobre alloca (): O compilador pode gerar verificações de limite de pilha que obterão acesso fora dos limites quando o acesso ao estilo de matriz for usado (não sei se algum compilador faz isso, mas pode ser feita) e a análise do código pode determinar se as expressões de acesso à matriz estão adequadamente delimitadas. Observe que, em alguns ambientes de programação, como automotivo, equipamentos médicos e aviônicos, essa análise deve ser feita mesmo para matrizes de tamanho fixo, tanto automáticas (na pilha) quanto alocação estática (global ou local).

Em arquiteturas que armazenam dados e retornam endereços / ponteiros de quadro na pilha (pelo que sei, são todos eles), qualquer variável alocada à pilha pode ser perigosa porque o endereço da variável pode ser obtido e valores de entrada não verificados podem permitir todos os tipos de travessuras.

A portabilidade é menos preocupante no espaço incorporado, no entanto, é um bom argumento contra o uso de alloca () fora de circunstâncias cuidadosamente controladas.

Fora do espaço incorporado, usei alloca () principalmente dentro das funções de registro e formatação para obter eficiência, e em um scanner lexical não recursivo, em que estruturas temporárias (alocadas usando alloca () são criadas durante a tokenização e a classificação e, em seguida, um persistente O objeto (alocado via malloc ()) é preenchido antes do retorno da função.O uso de alloca () para estruturas temporárias menores reduz bastante a fragmentação quando o objeto persistente é alocado.


1

A maioria das respostas aqui em grande parte esquece o ponto: há uma razão pela qual o uso _alloca()é potencialmente pior do que simplesmente armazenar objetos grandes na pilha.

A principal diferença entre o armazenamento automático e _alloca()o fato de este sofrer um problema adicional (sério): o bloco alocado não é controlado pelo compilador ; portanto, não há como otimizar ou reciclar o compilador.

Comparar:

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

com:

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

O problema com o último deve ser óbvio.


Você tem exemplos práticos demonstrando a diferença entre o VLA e alloca(sim, digo VLA, porque allocaé mais do que apenas criador de matrizes de tamanho estatístico)?
Ruslan

Existem casos de uso para o segundo, aos quais o primeiro não suporta. Talvez eu queira ter 'n' registros depois que o loop for executado 'n' times - talvez em uma lista ou árvore vinculada; essa estrutura de dados é descartada quando a função finalmente retornar. O que não quer dizer que eu iria código qualquer coisa que maneira :-)
Greggo

11
E eu diria que "o compilador não pode controlá-lo" é porque é dessa maneira que alloca () é definido; compiladores modernos sabem o que é aloca e o tratam especialmente; não é apenas uma função de biblioteca como era nos anos 80. Os VLAs C99 são basicamente alloca com escopo de bloco (e melhor digitação). Não mais ou menos controle, apenas em conformidade com diferentes semânticas.
Greggo

@greggo: Se você é o menos votado, eu ficaria feliz em saber por que você acha que minha resposta não é útil.
Alecov

Em C, a reciclagem não é tarefa do compilador, mas a tarefa da biblioteca c (free ()). alloca () é liberado no retorno.
peterh - Reinstala Monica

1

Eu não acho que alguém tenha mencionado isso, mas a alloca também tem alguns problemas sérios de segurança que não estão necessariamente presentes no malloc (embora esses problemas também surjam com qualquer matriz baseada em pilha, dinâmica ou não). Como a memória está alocada na pilha, os estouros / estouros de buffer têm consequências muito mais sérias do que apenas com malloc.

Em particular, o endereço de retorno para uma função é armazenado na pilha. Se esse valor for corrompido, seu código poderá ser criado para ir para qualquer região executável da memória. Os compiladores fazem um grande esforço para dificultar isso (em particular ao aleatorizar o layout do endereço). No entanto, isso é claramente pior do que apenas um estouro de pilha, pois o melhor caso é um SEGFAULT se o valor de retorno estiver corrompido, mas também pode começar a executar uma parte aleatória da memória ou, no pior caso, alguma região da memória que comprometa a segurança do programa. .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.