web-dev-qa-db-fra.com

Debug assertion a échoué! Expression: _pFirstBlock == pHead

J'appelle dans un .dll statiquement lié, et je vois cette erreur:

enter image description here

J'ai écrit le .dll et le code d'appel. Cette erreur ne devrait pas se produire. Je me demande si quelqu'un d'autre l'a déjà rencontré? Le fichier .dll ne contient que 10 lignes de code, c’est juste un test .dll pour voir comment les dll fonctionnent en général. Il explose lorsque je répète un std :: string hors du fichier .dll.

J'utilise Visual Studio 2012 et C++.

Ce que je vais essayer ensuite

De Assertion de débogage ... _pFirstBlock == pHead :

Ce problème peut se produire si l’on utilise les bibliothèques à un seul thread dans un fichier module multithread.

Demain, je vais essayer de recompiler les bibliothèques statiques Boost en mode multi-thread (mon .dll est défini sur le mode statique multi-thread).

Ce que je vais essayer ensuite

Voir L'utilisation de chaînes dans un objet exporté à partir d'une DLL provoque une erreur d'exécution :

Vous devez faire l'une des deux choses 

  1. Faites en sorte que la DLL et le client qui l'utilisent soient tous deux liés à la version DLL du tube cathodique (par exemple, pas de manière statique). 
  2. OU Vous devez vous assurer que vous ne transmettez pas de mémoire allouée dynamiquement (telle que contenue dans des objets chaîne) à travers les limites DLL . En d'autres termes, les fonctions exportées par la DLL ne renvoient pas la chaîne objets. 

Joe

Cela semble correspondre à ce qui se passe, il explose au point précis où je repasse une chaîne à travers une limite .dll. Le problème ne se produit que lorsque tout est lié en mode statique. Maintenant c'est réparable.

Voir Passer la référence au vecteur STL à la limite de dll .

Ce que je vais essayer ensuite

Voir Impossible de passer std :: wstring sur DLL .

Solution

J'ai une bonne solution, voir la réponse ci-dessous.

32
Contango

Dans ce cas, le problème est que je transmettais un std::string à travers une limite .dll.

Configuration de la bibliothèque d'exécution

  • Si MSVC Runtime library est défini sur Multi-threaded Debug DLL (/MDd), il n'y a pas de problème (cela fonctionne bien).

  • Si MSVC Runtime library est défini sur Multi-threaded Debug (/MTd), il générera alors cette erreur, qui peut être corrigée à l'aide des instructions suivantes.

Mémoire allouée dans le gestionnaire de mémoire A et libérée dans le gestionnaire de mémoire B ...

Le problème est que la mémoire est allouée du côté du fichier .dll, puis que cette même mémoire est libérée du côté de l'application. Cela signifie que le gestionnaire de mémoire A alloue de la mémoire et que le gestionnaire de mémoire B libère cette même mémoire, ce qui génère des erreurs.

La solution consiste à vérifier que toute la mémoire renvoyée est pas allouée dans la DLL. En d'autres termes, la mémoire est toujours allouée côté application et libérée côté application.

Bien entendu, la DLL peut allouer/libérer de la mémoire en interne, mais elle ne peut pas allouer de la mémoire libérée ultérieurement par l'application.

Exemples

Cela va pas travail:

// Memory is allocated on the .dll side, and freed on the app side, which throws error.
DLL std::string GetString(); 

Cela fonctionnera:

// Memory is allocated/freed on the application side, and never allocated in the .dll.
DLL int GetString(std::string& text); 

Cependant, cela ne suffit pas.

Du côté de l'application, la chaîne doit être pré-allouée:

std::string text("");
text.reserve(1024);     // Reserves 1024 bytes in the string "text".

Sur le côté .dll, le texte doit être copié dans le tampon d'origine (plutôt que d'être écrasé par la mémoire allouée sur le côté .dll):

text.assign("hello");

Parfois, C++ insistera de toute façon sur l’allocation de mémoire. Vérifiez à nouveau que la pré-allocation est toujours la même qu’elle était:

if (text.capacity < 1024)
{
   cout << "Memory was allocated on the .dll side. This will eventually throw an error.";
}

Une autre méthode efficace consiste à utiliser std::shared_ptr<std::string>. Ainsi, même si la mémoire est allouée dans le fichier .dll, elle est libérée par le fichier .dll (plutôt que du côté de l'application).

Une autre méthode consiste à accepter un char * et une longueur indiquant la quantité de mémoire préallouée. Si le texte à renvoyer est plus long que la longueur de la mémoire préallouée, renvoyez une erreur.

36
Contango

Voici à quoi ressemble assert () lorsque son argument expression est évalué à false. Cette assertion existe dans la construction Debug de la bibliothèque d'exécution C, conçue pour vérifier les problèmes d'allocation. La fonction free () dans votre cas. La construction Debug ajoute des vérifications supplémentaires pour vous assurer que vous écrivez votre code correctement. Et vous dire quand il détecte un problème. C'est comme appeler free () sur une allocation déjà libérée, c'est le cas simple. Ou appeler free () en passant la mauvaise valeur de pointeur, le cas le plus compliqué. Ou en appelant free () lorsque le tas était corrompu par du code antérieur, ce qui est beaucoup plus difficile. 

Ce n'est que dans la mesure où ils peuvent le prendre, ils ne savent pas réellement pourquoi votre code s'est trompé. Il n’ya aucun moyen de placer une grosse flèche rouge sur le code qui a corrompu le tas, par exemple. Le cas facile est couvert par la fenêtre de débogage Debug + Windows + Call Stack, il vous amène au code de votre programme qui a appelé free (). Ou std::operator delete pour un programme C++. Le cas le plus difficile est très, très dur en effet, la corruption de tas est souvent un Heisenbug. Faire en sorte que l'assertion soit répétable afin de pouvoir définir un point de rupture de données sur l'adresse indiquée est la stratégie principale. Croiser les doigts pour le cas facile, bonne chance avec ça!


After edit: oui, avoir des problèmes inter-modules avec une classe C++ comme std :: string est certainement l’un des problèmes qu’il peut résoudre. Pas un Heisenbug, bon genre de problème à avoir. Deux problèmes fondamentaux avec cela:

  • Les modules peuvent avoir chacun leur propre copie du tube cathodique, les objets alloués par un exemplaire du tube cathodique ne pouvant pas être libérés par un autre exemplaire du tube cathodique. Ils ont chacun leur propre tas qu'ils allouent. Un problème qui a été résolu dans VS2012, le CRT alloue maintenant à partir d'un tas de processus global.
  • Les modules peuvent ne pas utiliser la même implémentation de std :: string. Avec une mise en page d'objet qui ne correspond pas. Facilement induit par la compilation des modules avec différentes versions de bibliothèques C++, en particulier un problème lié aux modifications de C++ 11. Ou bien différents paramètres de construction, la macro _HAS_ITERATOR_DEBUGGING est assez notoire.

Le seul remède à ce problème consiste à s'assurer que vous construisez tous des modules de votre programme avec la même version du compilateur en utilisant exactement les mêmes paramètres de construction. L'utilisation de/MD est obligatoire, cela garantit que le CRT est partagé et qu'il n'y en a donc qu'un seul dans le programme.

3
Hans Passant

Cause probable: liaison avec une version incorrecte des DLL Qt, notamment lors du déplacement d'un projet de VS2010 à VS2012. 

Cela est dû aux différentes versions de la bibliothèque standard et aux problèmes d’allocation dynamique associés.

2

J'ai eu le même problème après une réinstallation de Windows. La compilation de ma bibliothèque d'exécution était un débogage multi-thread DLL (/MDd).

Ma solution était de supprimer le fichier *.user du projet Visual Studio, de supprimer le dossier de débogage et de reconstruire le projet.

1
user6118388