Quais são alguns dos motivos pelos quais uma compilation Release seria executada de maneira diferente de uma compilation de Depuração

Eu tenho um programa Visual Studio 2005 C ++ que é executado de forma diferente no modo de lançamento que no modo de debugging. No modo de liberação, ocorre uma falha intermitente (aparente). No modo de debugging, ele não falha. Quais são alguns dos motivos pelos quais uma compilation de lançamento funcionaria de maneira diferente de uma compilation de debugging?

Também vale a pena mencionar que meu programa é bastante complexo e usa várias bibliotecas de terceiros para processamento de XML, intermediação de mensagens, etc …

Desde já, obrigado!

Sobrevivendo a versão de lançamento dá uma boa visão geral.

Coisas que encontrei – a maioria já é mencionada

Inicialização variável, de longe, o mais comum. No Visual Studio, as compilações de debugging inicializam explicitamente a memory alocada para determinados valores, consulte, por exemplo, Valores de memory aqui. Esses valores geralmente são fáceis de detectar, causar um erro fora dos limites quando usado como um índice ou uma violação de access quando usado como um ponteiro. Um booleano não inicializado é verdade, no entanto, e pode causar bugs de memory não inicializados que não são detectados por anos.

Em versões do Release, onde a memory não é explicitamente inicializada, apenas mantém o conteúdo anterior. Isso leva a “valores engraçados” e a falhas “aleatórias”, mas com frequência a falhas determinísticas que exigem que um comando aparentemente não relacionado seja executado antes do comando que realmente falha. Isso é causado pelo primeiro comando “configurar” o local da memory com valores específicos e, quando os locais da memory são reciclados, o segundo comando os vê como inicializações. Isso é mais comum em variables ​​de pilha não inicializadas do que em heap, mas o último também aconteceu comigo.

A boot da memory bruta também pode ser diferente em uma compilation de lançamento, seja iniciando no visual studio (debugger attached) vs. iniciando no explorer. Isso faz com que o tipo de release “mais legal” construa erros que nunca aparecem sob o depurador.

Otimizações válidas vêm em segundo lugar na minha experiência. O padrão C ++ permite que muitas otimizações ocorram, o que pode ser surpreendente, mas são inteiramente válidas, por exemplo, quando dois pointers são usados ​​na mesma localização de memory, ordem de boot não é considerada ou vários segmentos modificam os mesmos locais de memory e você espera uma determinada ordem em que o encadeamento B vê as alterações feitas pelo encadeamento A. Freqüentemente, o compilador é culpado por isso. Não tão rápido, jovem yedi! – ver abaixo

As compilações Timing Release não são apenas “executadas mais rapidamente”, por diversas razões (otimizações, funções de log fornecendo um ponto de synchronization de thread, código de debugging como assert não executadas, etc.) e o tempo relativo entre operações muda drasticamente. O problema mais comum descoberto por isso são condições de corrida, mas também deadlocks e execução simples de “ordem diferente” de código baseado em mensagem / timer / evento. Mesmo que sejam problemas de temporização, eles podem ser surpreendentemente estáveis ​​em compilações e plataformas, com reproduções que “funcionam sempre, exceto no PC 23”.

Bytes de guarda . As compilações de debugging geralmente colocam (mais) bytes de proteção em torno de instâncias e alocações selecionadas, para proteger contra estouros de índice e, às vezes, underflows. Nos raros casos em que o código depende de compensações ou tamanhos, por exemplo, serializando estruturas brutas, elas são diferentes.

Outras diferenças de código Algumas instruções – por exemplo, afirma – avaliam nada em compilações de lançamento. Às vezes eles têm efeitos colaterais diferentes. Isso é predominante com truques de macro, como no clássico (aviso: vários erros)

#ifdef DEBUG #define Log(x) cout << #x << x << "\n"; #else #define Log(x) #endif if (foo) Log(x) if (bar) Run(); 

Que, em uma compilation de release, avalia se (foo && bar) Esse tipo de erro é muito raro com código C / C ++ normal e macros que são escritas corretamente.

Compiler Bugs Isso realmente nunca acontece. Bem, isso acontece, mas você é melhor para a maior parte de sua carreira supondo que não. Em uma década de trabalho com o VC6, encontrei um em que ainda estou convencido de que se trata de um erro de compilador não corrigido, comparado a dezenas de padrões (talvez até centenas de instâncias) com compreensão insuficiente da escritura (também conhecida como padrão).

Na versão de debugging, muitas vezes as declarações e / ou os símbolos de debugging são ativados. Isso pode levar a um layout de memory diferente. No caso de um ponteiro defeituoso, estouro de uma matriz ou access similar à memory, você acessa em um caso uma memory crítica ruim (por exemplo, ponteiro de function) e em outro caso talvez apenas alguma memory não crítica (por exemplo, apenas uma seqüência de doc é descartada)

Variáveis ​​que não foram inicializadas explicitamente serão zeradas ou não na compilation Release.

A compilation de lançamento (esperançosamente) seria executada mais rapidamente do que sua compilation de debugging. Se você estiver usando mais de um thread, poderá ver mais intercalação ou simplesmente um thread executando mais rápido do que os outros, o que talvez você não tenha percebido na compilation de debugging.

Eu tive um problema semelhante não muito tempo atrás , que acabou sendo causado pela pilha sendo tratada de forma diferente em versões de lançamento. Outras coisas que podem diferir:

  • Alocação de memory é tratada de forma diferente com compilações de debugging no compilador VS (ou seja, escrevendo 0xcc sobre a memory limpa, etc.)
  • Loop unrolling e outras otimizações de compilador
  • Alívio de pointers

Depende tanto do fornecedor do compilador quanto das bibliotecas que você compila com os sinalizadores DEBUG. Embora o código DEBUG nunca deva afetar o código de execução (não deve ter efeitos colaterais), às vezes isso acontece.

Em particular, as variables ​​podem ser inicializadas apenas no modo DEBUG e deixadas não inicializadas no modo RELEASE. O STL em compiladores do Visual Studio é diferente nos modos DEBUG e RELEASE. A ideia é que os iteradores sejam totalmente verificados no DEBUG para detectar possíveis erros (usando iteradores invalidados, por exemplo, um iterador em um vetor é invalidado se uma inserção ocorrer após o iterador ser recuperado).

O mesmo acontece com bibliotecas de terceiros, a primeira que eu posso pensar é QT4, que terminará seu programa com uma declaração se um thread diferente daquele que criou o object gráfico executa operações de pintura.

Com todas as alterações, o seu código e o tamanho da memory serão diferentes nos dois modos. Um problema de ponteiro (lendo a posição de um passar o fim de uma matriz) pode passar despercebido se essa posição for legível.

Asserções são destinadas a matar o aplicativo durante o DEBUG e desaparecer das compilações RELEASE, então eu não pensaria em asserções como sendo o seu problema. Um ponteiro desonesto ou um access após o final seriam meus primeiros suspeitos.

Algum tempo atrás, houve problemas com algumas otimizações do compilador quebrando código, mas eu não li as reclamações ultimamente. Poderia haver um problema de otimização lá, mas isso não seria meu primeiro suspeito.

As compilações de versão geralmente são compiladas com a otimização ativada no compilador, enquanto as compilações de debugging geralmente não são.

Em alguns idiomas ou ao usar muitas bibliotecas diferentes, isso pode causar falhas intermitentes – especialmente quando o nível de otimização escolhido é muito alto.

Eu sei que este é o caso com o compilador gcc C ++, mas não tenho certeza sobre o compilador da Microsoft.

http://www.debuginfo.com/tips/userbpntdll.html

Devido ao fato de que bytes de proteção são adicionados em compilações de debugging, você poderá acessar “com segurança” a memory que está fora dos limites de uma matriz (particularmente matrizes dinâmicas), mas isso causará uma violação de access na compilation de lançamento . Esse erro pode passar despercebido, causando um heap corrompido e possivelmente uma violação de access em um local não relacionado ao erro original.

Use PageHeap (ou, se você tiver ferramentas de debugging instaladas, poderá usar o gflags) para descobrir bugs relacionados a pilhas corrompidas.

http://support.microsoft.com/?id=286470

Na minha experiência, o motivo mais comum parece ser que as configurações diferem em mais maneiras do que as configurações de release / build. Por exemplo, libs diferentes são incluídas, ou a compilation de debugging tem um tamanho de pilha diferente da compilation de lançamento.

Para evitar isso em nossos projetos do Visual Studio 2005, usamos extensivamente as folhas de propriedades. Dessa forma, as configurações de liberação e debugging podem compartilhar configurações comuns.

Este post, juntamente com os links fornecidos, é muito útil para corrigir o bug relacionado. adicionando à lista acima, a diferença nas convenções de chamada também pode levar a esse comportamento – Ele falhou na compilation de lançamento apenas com otimização para mim. Eu declarei como __stdcall e definido como __cdecl (por padrão). [estranhamente este aviso não é escolhido mesmo em alertar o nível 4 MSVC?]