58 votes

Quelles sont les raisons pour lesquelles un build Release peut fonctionner différemment d'un build Debug ?

J'ai un programme Visual Studio 2005 C++ qui s'exécute différemment en mode Release et en mode Debug. En mode Release, il y a un crash (apparent) intermittent. En mode débogage, il ne se plante pas. Quelles sont les raisons pour lesquelles une version Release fonctionne différemment d'une version Debug ?

Il convient également de mentionner que mon programme est assez complexe et utilise plusieurs bibliothèques tierces pour le traitement XML, le courtage de messages, etc...

Merci d'avance !

2 votes

Bien que j'aie essayé de répondre à votre question, cela n'aide peut-être pas beaucoup à résoudre le problème. Premièrement, obtenez un bon reproducteur. Ensuite, activez les informations de débogage dans le build de la version. Le crash est toujours là ? non --> peut être le timing ou l'initialisation. Oui --> utiliser un débogueur (distant).

130voto

peterchen Points 21792

Survivre à la version de lancement donne un bon aperçu.

Les choses que j'ai rencontrées - la plupart sont déjà mentionnées

Initialisation des variables de loin la plus courante. Dans Visual Studio, les builds de débogage initialisent explicitement la mémoire allouée à des valeurs données, voir par ex. Valeurs de la mémoire ici. Ces valeurs sont généralement faciles à repérer, elles provoquent une erreur hors limites lorsqu'elles sont utilisées comme index ou une violation d'accès lorsqu'elles sont utilisées comme pointeur. Un booléen non initialisé est vrai, cependant, et peut causer des bogues de mémoire non initialisée passant inaperçus pendant des années.

Dans les versions où la mémoire n'est pas explicitement initialisée, elle conserve simplement le contenu qu'elle avait auparavant. Cela conduit à de "drôles de valeurs" et à des plantages "aléatoires", mais aussi souvent à des plantages déterministes qui nécessitent l'exécution d'une commande apparemment sans rapport avec la commande qui provoque le plantage. Cela est dû au fait que la première commande "configure" l'emplacement mémoire avec des valeurs spécifiques, et que lorsque les emplacements mémoire sont recyclés, la seconde commande les voit comme des initialisations. C'est plus courant avec les variables de pile non initialisées qu'avec le tas, mais le dernier cas m'est aussi arrivé.

L'initialisation de la mémoire brute peut également être différente dans une version publiée si vous démarrez à partir de Visual Studio (débogueur attaché) ou à partir de l'Explorateur. Cela crée des bogues dans les versions les plus "belles" qui n'apparaissent jamais dans le débogueur.

Optimisations valides viennent en second lieu dans mon expérience. La norme C++ permet de nombreuses optimisations qui peuvent surprendre mais qui sont tout à fait valables, par exemple lorsque deux pointeurs aliasent le même emplacement mémoire, l'ordre d'initialisation n'est pas pris en compte, ou lorsque plusieurs threads modifient les mêmes emplacements mémoire, et que l'on s'attend à ce que le thread B voie dans un certain ordre les modifications apportées par le thread A. Souvent, le compilateur est blâmé pour cela. Pas si vite, jeune yedi ! - voir ci-dessous

Timing Les versions ne sont pas seulement "plus rapides", pour diverses raisons (optimisations, fonctions de journalisation fournissant un point de synchronisation des threads, code de débogage comme les assertions non exécutées, etc.), le timing relatif entre les opérations change aussi considérablement. Le problème le plus courant découvert par ce biais est celui des conditions de course, mais aussi des blocages et de la simple exécution "dans un ordre différent" de code basé sur des messages, des temps ou des événements. Même s'il s'agit de problèmes de timing, ils peut être étonnamment stables d'une version à l'autre et d'une plate-forme à l'autre, avec des reproductions qui "fonctionnent toujours, sauf sur PC 23".

Octets de garde . Les constructions de débogage mettent souvent des octets de garde (plus nombreux) autour d'instances et d'allocations sélectionnées, pour se protéger contre les débordements d'index et parfois les sous-débordements. Dans les rares cas où le code s'appuie sur des offsets ou des tailles, par exemple pour sérialiser des structures brutes, ils sont différents.

Autres différences de code Certaines instructions - par exemple les assertions - n'ont aucune valeur dans les builds de version. Parfois, elles ont des effets secondaires différents. Ceci est courant avec les macros, comme dans la classique (avertissement : erreurs multiples)

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

if (foo)
  Log(x)
if (bar)
  Run();

Ce qui, dans un build de version, correspond à si (foo && bar) Ce type d'erreur est très très rare avec du code C/C++ normal, et des macros qui sont correctement écrites.

Bugs du compilateur Cela n'arrive vraiment jamais. Eh bien, cela arrive, mais il est préférable, pour la majeure partie de votre carrière, de supposer que ce n'est pas le cas. En dix ans de travail avec la VC6, j'en ai trouvé un où je suis toujours convaincu qu'il s'agit d'un bogue de compilateur non corrigé, par rapport à des dizaines de modèles (peut-être même des centaines d'instances) avec une compréhension insuffisante de l'écriture (alias la norme).

2 votes

Excellente réponse +1 ! Suggestion acceptée

2 votes

Fabuleux, mais ajoutez quelque chose sur les effets secondaires des assertions. L'expression dans une assertion peut avoir un effet secondaire, et ne sera pas exécutée dans la version finale. Par exemple, cas pathologique : assert(n++ == 2) ; Parfois, il existe une macro alternative qui évalue toujours l'expression, par exemple VERIFY dans VC++.

1 votes

Oh, je vois que l'article lié le mentionne...

6voto

flolo Points 8757

Dans la version de débogage, les assertions et/ou les symboles de débogage sont souvent activés. Cela peut conduire à une disposition différente de la mémoire. Dans le cas d'un pointeur erroné, du débordement d'un tableau ou d'un accès à la mémoire similaire, vous accédez dans un cas à une mémoire critique (par exemple, un pointeur de fonction) et dans l'autre cas, peut-être juste à une mémoire non critique (par exemple, une simple chaîne de caractères est détruite).

5voto

Burkhard Points 6734

Les variables qui ne sont pas initialisées explicitement seront ou ne seront pas remises à zéro lors de la construction de la version.

2voto

Eugene Yokota Points 43213

La version finale (si tout va bien) sera plus rapide que votre version de débogage. Si vous utilisez plus d'un fil d'exécution, vous pourriez voir plus d'entrelacement, ou simplement un fil d'exécution plus rapide que les autres, ce que vous n'avez peut-être pas remarqué dans la version de débogage.

2voto

fluffels Points 1748

Les versions de lancement sont généralement compilées avec l'optimisation activée dans le compilateur, alors que les versions de débogage ne le sont généralement pas.

Dans certaines langues ou lors de l'utilisation de nombreuses bibliothèques différentes, cela peut provoquer des plantages intermittents, en particulier lorsque le niveau d'optimisation choisi est très élevé.

Je sais que c'est le cas avec le compilateur C++ gcc, mais je ne suis pas sûr du compilateur de Microsoft.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X