Pergunta

Existem certas condições que podem causar estouros de pilha em um sistema Linux x86:

  • struct my_big_object[HUGE_NUMBER] na pilha.Passar por ele eventualmente causa SIGSEGV.
  • O alloca() rotina (como malloc(), mas usa a pilha, se libera automaticamente e também explode com SIGSEGV se for muito grande). Atualizar:alloca() não está formalmente obsoleto como afirmei originalmente;é apenas desencorajado.

Existe uma maneira de detectar programaticamente se a pilha local é grande o suficiente para um determinado objeto?Eu sei que o tamanho da pilha é ajustável via ulimit, então espero que haja uma maneira (por mais não portátil que seja).Idealmente, eu gostaria de poder fazer algo assim:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}
Foi útil?

Solução

Você pode determinar o espaço de pilha que o processo tem disponível encontrando o tamanho do espaço de pilha de um processo e subtraindo a quantidade usada.

ulimit -s

mostra o tamanho da pilha em um sistema Linux.Para uma abordagem programática, confira getrlimit().Em seguida, para determinar a profundidade atual da pilha, subtraia um ponteiro para o topo da pilha de um para o fundo.Por exemplo (código não testado):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}

Outras dicas

A rotina de aloca () depreciada (como Malloc (), mas usa a pilha, se liberta automaticamente e também explode com o Sigsegv se for muito grande).

Por que alloca está obsoleto?

De qualquer forma, quão mais rápido no seu caso é alloca vs malloc?(Vale a pena?)

E você não recebe null de alloca se não houver espaço suficiente?(da mesma forma que malloc?)

E quando o seu código trava, onde ele trava?está em alloca ou em doStuff ()?

/Johan

Não tenho certeza se isso se aplica ao Linux, mas no Windows é possível encontrar violações de acesso com grandes alocações de pilha mesmo que tenham sucesso!

Isso ocorre porque, por padrão, o VMM do Windows marca apenas as primeiras (não tenho certeza de quantas exatamente) páginas de 4.096 bytes da pilha RAM como pagináveis ​​(ou seja,apoiado pelo arquivo de paginação), pois acredita que os acessos à pilha geralmente marcharão de baixo para cima;à medida que os acessos se aproximam cada vez mais do "limite" atual, as páginas cada vez mais baixas são marcadas como pagináveis.Mas isso significa que uma leitura/gravação de memória muito abaixo do topo da pilha irá desencadear uma violação de acesso, pois essa memória ainda não foi alocada!

alloca() retornará NULL em caso de falha, acredito que o comportamento de alloca(0) é indefinido e variante de plataforma.Se você verificar isso antes de do_something(), você nunca deverá ser atingido por um SEGV.

Eu tenho algumas perguntas:

  1. Por que, ah, por que você precisa de algo tão grande na pilha?O tamanho padrão na maioria dos sistemas é 8M, ainda é muito pequeno?
  2. Se a função que chama alloca() for bloqueada, a proteção da mesma quantidade de heap via mlock() / mlockall() garantiria quase o mesmo desempenho de acesso (ou seja,"Não me troque, mano!") ao longo do tempo?Se você estiver usando um agendador 'rt' mais agressivo, é recomendável chamá-lo de qualquer maneira.

A questão é interessante, mas levanta uma sobrancelha.Ele levanta a agulha do meu medidor de furo redondo e quadrado.

Você não diz muito sobre por que deseja alocar na pilha, mas se o modelo de memória da pilha for atraente, você também poderá implementar a alocação de pilha no heap.Aloque uma grande quantidade de memória no início do programa e mantenha uma pilha de ponteiros para isso, que corresponderiam aos quadros da pilha normal.Você só precisa se lembrar de exibir o ponteiro da pilha privada quando a função retornar.

Vários compiladores, por exemplo Abra o Watcom C/C++, suporta a função stackavail() que permite fazer exatamente isso

Você pode usar GNU libsigsegv para lidar uma falha de página, incluindo casos em que ocorre um estouro de pilha (de seu site):

Em alguns aplicativos, o manipulador de estouro de pilha executa alguma limpeza ou notifica o usuário e encerra imediatamente o aplicativo.Em outros aplicativos, o manipulador de estouro de pilha longjmps volta para um ponto central no aplicativo.Esta biblioteca suporta ambos os usos.No segundo caso, o manipulador deve garantir a restauração da máscara de sinal normal (porque muitos sinais são bloqueados enquanto o manipulador é executado) e também deve chamar sigsegv_leave_handler() para transferir o controle;só então ele poderá se afastar por muito tempo.

A função aloca é não descontinuada.No entanto, não está no POSIX e também depende da máquina e do compilador.A página de manual do Linux para alloca observa que "para certos aplicativos, seu uso pode melhorar a eficiência em comparação ao uso de malloc e, em certos casos, também pode simplificar a desalocação de memória em aplicativos que usam longjmp() ou siglongjmp().Caso contrário, seu uso é desencorajado.”

A página de manual também diz que "não há indicação de erro se o quadro da pilha não puder ser estendido.No entanto, após uma falha na alocação, o programa provavelmente receberá um SIGSEGV.”

O desempenho do malloc foi realmente mencionado no Podcast Stackoverflow nº 36.

(Sei que esta não é uma resposta adequada à sua pergunta, mas achei que poderia ser útil de qualquer maneira.)

Mesmo que esta não seja uma resposta direta à sua pergunta, espero que você esteja ciente da existência de valgrind - uma ferramenta maravilhosa para detectar tais problemas em tempo de execução, no Linux.

Em relação ao problema da pilha, você pode tentar alocar objetos dinamicamente de um pool fixo que detecte esses overflows.Com um simples macro-assistente você pode fazer isso rodar em tempo de depuração, com código real rodando em tempo de lançamento, e assim saber (pelo menos para os cenários que você está executando) que não está demorando muito. Aqui estão mais informações e um link para uma implementação de amostra.

Não consigo pensar em uma maneira legal.Talvez seja possível usando getrlimit() (sugerido antes) e alguma aritmética de ponteiros?Mas primeiro pergunte a si mesmo se você realmente quer isso.

void *closeToBase;

main () {
  int closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  int currentTop;
  return getrlimit(...) - (¤tTop  - closeToBase) > bytes + SomeExtra;
}

Pessoalmente, eu não faria isso.Aloque coisas grandes na pilha, a pilha não foi feita para isso.

O final da área da pilha é determinado dinamicamente pelo sistema operacional.Embora você possa encontrar os limites "estáticos" da pilha observando as áreas de memória virtual (VMAs) de uma forma altamente dependente do sistema operacional (consulte os arquivos stackvma* em libsigsegv/src/), você também terá que considerar

Peço desculpas se isso é óbvio, mas você poderia facilmente escrever uma função para testar um tamanho de alocação de pilha específico apenas tentando a alocação (desse tamanho) e capturando uma exceção de estouro de pilha.Se você quiser, poderá colocá-lo em uma função, com alguma matemática pré-determinada para a sobrecarga da pilha de funções.Por exemplo:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top