2010-04-09 6 views
9

Visual Studio hat eine Codeanalyse (/analyze) für C/C++ hinzugefügt, um bei der Identifizierung von fehlerhaftem Code zu helfen. Das ist eine nette Sache, aber wenn Sie mit einem alten Projekt umgehen, können Sie von der Anzahl der Warnungen überwältigt sein.Wie schreibe ich richtig ASSERT Code um zu übergeben/analysieren in msvc?

Die meisten Probleme werden generiert, weil der alte Code am Anfang der Methode oder Funktion ein ASSERT ausführt.

Ich denke, dies ist die ASSERT Definition ist im Code verwendet (von afx.h)

#define ASSERT(f)   DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0))) 

Beispielcode:

ASSERT(pBytes != NULL); 
*pBytes = 0; // <- warning C6011: Dereferencing NULL pointer 'pBytes' 

Ich suche eine einfache, saubere und sichere Lösung lösche diese Warnungen, die nicht bedeuten, diese Warnungen zu deaktivieren. Habe ich erwähnt, dass es in der aktuellen Codebasis viele Vorkommen gibt?

+5

Können Sie einige der Warnmeldungen anzeigen? –

+2

Können Sie auch Ihre Implementierung von 'ASSERT' zeigen? –

+0

'/ analyze' beschwert sich nicht über' ASSERT' auf meinem Rechner. –

Antwort

2

PREFast sagt Ihnen, dass Sie einen Fehler in Ihrem Code haben; Ignoriere es nicht. Sie haben tatsächlich eine, aber Sie haben nur herumgesprochen und es bestätigt. Das Problem ist das: nur weil pBytes in der Entwicklung nie NULL war & Testen bedeutet nicht, dass es nicht in Produktion sein wird. Du gehst mit dieser Eventualität nicht klar. PREfast kennt das und versucht, Sie zu warnen, dass Produktionsumgebungen feindlich sind und Ihren Code eine rauchende, verstümmelte Masse wertloser Bytes hinterlassen.

/rant

Es gibt zwei Möglichkeiten, dieses Problem zu beheben: der richtige Weg, und ein Hack.

Der richtige Weg ist NULL-Zeiger zur Laufzeit zu handhaben:

void DoIt(char* pBytes) 
{ 
    assert(pBytes != NULL); 
    if(!pBytes) 
     return; 
    *pBytes = 0; 
} 

Dies wird zum Schweigen PREfast.

Der Hack soll eine Anmerkung verwenden. Zum Beispiel:

void DoIt(char* pBytes) 
{ 
    assert(pBytes != NULL); 
    __analysis_assume(pBytes); 
    *pBytes = 0; 
} 

EDIT: Here's a link beschreibt PREfast Anmerkungen. Ein Ausgangspunkt sowieso.

+0

Ich hatte Angst, dass dies die einzige Lösung ist, da es ziemlich viel Code erfordert, um hinzuzufügen. – sorin

+0

Eine andere Option könnte sein, ASSERT neu zu definieren, um die Annotation einzuschließen. Besser wäre es, ASSERT neu zu definieren, so dass es in Release läuft und die NULL-Zeiger in sinnvoller Weise behandelt. –

+8

Äh, Sie gehen blind davon aus, dass 'pBytes' * tatsächlich' NULL' sein kann. Das ist nicht unbedingt eine gültige Annahme. PREfast spielt nur auf Nummer sicher: Es gibt oft falsche Positive. Deshalb ist es standardmäßig nicht aktiviert. Es zeigt Ihnen, was es verdächtig findet, und es liegt an Ihnen zu überprüfen, ob es korrekt ist, und wenn ja, beheben Sie das Problem. Aber zu sagen, dass Sie tatsächlich den NULL-Fall behandeln müssen, nur weil PREfast so sagt, ist reiner Abfall. Handle es, wenn es tatsächlich logisch auftreten kann. – jalf

-1

Sie scheinen davon auszugehen, dass ASSERT(ptr) irgendwie bedeutet, dass ptr danach nicht NULL ist. Das ist nicht wahr, und der Code-Analyzer macht diese Annahme nicht.

+0

Das stimmt, aber es beantwortet die Frage nicht. – sorin

5

/analyze kann nicht garantiert werden, dass relevante und korrekte Warnungen ausgegeben werden. Es kann und wird eine Menge von Problemen vermissen, und es gibt auch eine Anzahl von falsch positiven (Dinge, die es als Warnungen identifiziert, aber die vollkommen sicher sind und nie tatsächlich auftreten werden)

Es ist unrealistisch zu erwarten, dass Null Warnungen mit/analysieren.

Es hat eine Situation aufgezeigt, in der Sie einen Zeiger dereferenzieren, der es nicht überprüfen kann, ist immer gültig. Soweit PREfast es beurteilen kann, gibt es keine Garantie, dass es nie NULL sein wird.

Aber das heißt nicht kann NULL sein. Nur dass die Analyse, die erforderlich ist, um zu beweisen, dass es sicher ist, für PREfast zu komplex ist.

Möglicherweise können Sie die Microsoft-spezifische Erweiterung __assume verwenden, um dem Compiler mitzuteilen, dass diese Warnung nicht ausgegeben werden soll, aber eine bessere Lösung besteht darin, die Warnung zu verlassen. Jedes Mal, wenn Sie mit/compile kompilieren (was nicht bei jeder Kompilierung erforderlich sein muss), sollten Sie sicherstellen, dass die Warnungen, die auftauchen, immer noch falsch sind.

Wenn Sie verwenden behaupten richtig (logische Fehler bei der Programmierung zu fangen, gegen Situationen Bewachung dass nicht passieren, dann habe ich kein Problem mit Ihrem Code zu sehen, oder die Warnung mit verlassen. Code Hinzufügen ein Problem zu handhaben, dass nie auftreten ist nur sinnlos.Sie fügen mehr Code und mehr Komplexität ohne Grund (wenn es nie auftreten kann, dann haben Sie keine Möglichkeit, davon zu erholen, weil Sie absolut keine Ahnung haben, in welchem ​​Zustand das Programm sein wird Alles, was Sie wissen, ist, dass es einen Codepfad eingegeben hat, den Sie für unmöglich hielten.

Wenn Sie jedoch Ihre Assert als tatsächliche Fehlerbehandlung verwenden (der Wert kann in Ausnahmefällen NULL sein, Sie erwarten nur, dass es nicht passieren wird), dann ist es ein Fehler in Ihrem Code. Dann ist eine ordnungsgemäße Fehlerbehandlung (normalerweise Ausnahmen) erforderlich.

Niemals jemals für Probleme, die möglich sind. Verwenden Sie sie, um zu verifizieren, dass die unmöglich nicht geschieht. Und wenn/analyse dir Warnungen gibt, sieh sie dir an. Wenn es falsch positiv ist, ignoriere es (unterdrücke es nicht, denn während es heute falsch positiv ist, kann der Code, den du morgen eincheckst, möglicherweise ein echtes Problem werden).

+0

Ich versuche nicht, einen Krieg mit Ihnen zu beginnen, aber ich glaube, dass dies ein sehr schlechter Rat ist: "Eine bessere Lösung ist es, die Warnung zu verlassen. Jedes Mal, wenn Sie kompilieren/analysieren [...], sollten Sie dies überprüfen Die Warnungen, die es hervorbringt, sind immer noch falsch positive. " Meine Argumentation im nächsten Kommentar ... –

+2

Ich bin der Meinung, dass Builds benötigt werden, um mit Null-Fehlern und Null-Warnungen auf den höchsten Warnstufen (inklusive/Analyse) zu kompilieren, bevor ein Check-In erlaubt ist. Viele Gründe, aber eine sehr einfache ist, wenn Warnungen bleiben dürfen, dann können Sie mit Hunderten oder Tausenden von Warnungen enden. Es ist unrealistisch zu erwarten, dass jedes Mal, wenn ein Produktions-Build erstellt wird, die Gültigkeit überprüft wird. Es wird einfach nicht passieren, den Zweck der Warnungen überhaupt zu besiegen. –

+2

@John: Deswegen kompiliert man normalerweise nicht mit/analyze. Aber natürlich, wenn Sie sogar mit/ohne Analyse keine Warnungen vorziehen, können Sie die Warnungen stummschalten, sobald sie sich als falsch-positiv erwiesen haben. Mein Hauptpunkt ist einfach, dass Sie keine Probleme behandeln müssen, die niemals auftreten können. Ob du die Warnung stumm machst oder nicht, liegt an dir. Ich würde es vorziehen, es zu behalten, und nur gelegentlich laufen/analysieren, weil PREfast * nicht * genau sein soll oder null falsche positive Ergebnisse liefert. Es soll paranoid sein, und deshalb möchte ich * ab und zu * seine Ausgabe verifizieren. – jalf

0

Denken Sie daran, ASSERT() geht weg in einem Einzelhandel bauen, so dass die Warnung C6011 in Ihrem Code oben absolut korrekt ist: Sie müssen überprüfen, dass pBytes nicht Null ist, sowie die ASSERT(). Das ASSERT() wirft Ihre App einfach in einen Debugger, wenn diese Bedingung in einem Debug-Fehler erfüllt ist.

Ich arbeite sehr viel an/analysieren und PREfast, also wenn Sie andere Fragen haben, fühlen Sie sich bitte, lassen Sie es mich wissen.

+3

Sie gehen zu sehr vom OP-Code aus. Ja, das Assert geht in einem Release-Build verloren, so dass wir uns nicht darauf verlassen können, dass der Assert alleine überprüft, dass der Zeiger nicht null ist. Aber vermutlich, wenn die Assert verwendet wird, dann liegt es daran, dass der Programmierer * mit anderen Mitteln * weiß, dass der Zeiger niemals Null sein kann. Wenn dies der Fall ist, wenn das Assert richtig verwendet wird, dann ist dies einfach ein falsch positives Ergebnis. Wenn er sich tatsächlich darauf verlässt, dass die assert NULL-Zeiger auffängt, die * tatsächlich * auftreten, wie Sie vermuten, dann missbraucht er die Assert und sollte sie durch eine korrekte Fehlerbehandlung ersetzen. – jalf

+0

Was ist eine gute Möglichkeit, Prefast-Annotationen einzubeziehen, aber den Code weiterhin mit GCC erstellen zu lassen? – Setheron

-2

Mein Co-Autor David LeBlanc würde mir sagen, dieser Code ist sowieso gebrochen, Sie verwenden C++ vorausgesetzt, sollten Sie einen Verweis eher als ein Zeiger und ein ref kann nicht NULL verwenden :)

3

Zunächst muss Ihre Assertion-Anweisung garantieren, die Anwendung zu werfen oder zu beenden. Nach einigen Experimenten, die ich in diesem Fall gefunden habe/analyze ignoriert alle Implementierungen entweder in Template-Funktionen, Inline-Funktionen oder normalen Funktionen. Sie müssen stattdessen Makros und die Do {} while (0) Trick, mit Inline-Unterdrückung von

Wenn Sie die Definition von ATLENSURE() Microsoft verwenden __analyse_assume() in ihrem Makro, sie haben auch mehrere Absätze von sehr Gute Dokumentation darüber, warum und wie sie ATL migrieren, um dieses Makro zu verwenden.

Als Beispiel habe ich die CPPUNIT_ASSERT-Makros auf die gleiche Weise modifiziert, um Tausende von Warnungen in unseren Unit-Tests zu bereinigen.

#define CPPUNIT_ASSERT(condition)                 \ 
do { (CPPUNIT_NS::Asserter::failIf(!(condition),            \ 
            CPPUNIT_NS::Message("assertion failed"),       \ 
            CPPUNIT_SOURCELINE())); __analysis_assume(!!(condition));  \ 
            __pragma(warning(push))           \ 
            __pragma(warning(disable: 4127))        \ 
            } while(0)               \ 
            __pragma(warning(pop))