Wie programmieren Sie sicher außerhalb einer verwalteten Codeumgebung?

8

Wenn Sie jemand sind, der in C oder C ++ programmiert, ohne die Vorteile der Speicherverwaltung in der verwalteten Sprache, Typprüfung oder Pufferüberlaufschutz, mithilfe von Zeigerarithmetik, wie stellen Sie sicher, dass Ihre Programme sicher sind? Verwenden Sie viele Unit-Tests oder sind Sie nur ein vorsichtiger Coder? Hast du andere Methoden?

    
Robert Harvey 07.10.2009, 15:07
quelle

9 Antworten

24

Alles oben genannte. Ich benutze:

  1. Sehr vorsichtig
  2. Smart Pointer so viel wie möglich
  3. Datenstrukturen, die getestet wurden, viele Standardbibliothek
  4. Unit testet die ganze Zeit
  5. Speichervalidierungstools wie MemValidator und AppVerifier
  6. Bete jede Nacht, es stürzt nicht auf Kundenseite.

Eigentlich übertreibe ich nur. Es ist nicht zu schlecht und es ist nicht wirklich schwierig, die Kontrolle über Ressourcen zu behalten, wenn Sie Ihren Code richtig strukturieren.

Interessante Notiz. Ich habe eine große Anwendung, die DCOM verwendet und verwaltete und nicht verwaltete Module hat. Die unmanaged Module sind in der Regel schwieriger zu debuggen während der Entwicklung, aber am Kundenstandort aufgrund der vielen Tests sehr gut. Die verwalteten Module leiden manchmal unter schlechtem Code, da der Garbage Collector so flexibel ist, dass Programmierer die Ressourcennutzung überprüfen.

    
Andrew Keith 07.10.2009, 05:39
quelle
16

Ich benutze viele und viele Behauptungen und baue sowohl eine "Debug" -Version als auch eine "Release" -Version. Meine Debug-Version läuft viel langsamer als meine Release-Version, mit all den Prüfungen, die es tut.

Ich laufe häufig unter Valgrind und mein Code hat keine Speicherlecks. Null. Es ist viel einfacher, ein Programm leckfrei zu halten, als ein fehlerhaftes Programm zu nehmen und alle Lecks zu beheben.

Auch mein Code kompiliert ohne Warnungen, trotz der Tatsache, dass ich den Compiler für zusätzliche Warnungen gesetzt habe. Manchmal sind die Warnungen albern, aber manchmal zeigen sie direkt auf einen Fehler, und ich repariere es, ohne dass ich es im Debugger finden muss.

Ich schreibe reines C (ich kann in diesem Projekt kein C ++ verwenden), aber ich mache C sehr konsequent. Ich habe objektorientierte Klassen mit Konstruktoren und Destruktoren; Ich muss sie mit der Hand anrufen, aber die Konsistenz hilft. Und wenn ich vergesse, einen Destruktor zu rufen, schlägt Valgrind mir über den Kopf, bis ich es behoben habe.

Zusätzlich zum Konstruktor und Destruktor schreibe ich eine Selbstüberprüfungsfunktion, die über das Objekt schaut und entscheidet, ob es vernünftig ist oder nicht; Wenn beispielsweise ein Datei-Handle null ist, aber verknüpfte Dateidaten nicht auf Null gesetzt sind, weist dies auf eine Art Fehler hin (entweder der Handle wurde verfälscht oder die Datei wurde nicht geöffnet, aber diese Felder im Objekt enthalten Papierkorb). Außerdem haben die meisten meiner Objekte ein "Signatur" -Feld, das auf einen bestimmten Wert (spezifisch für jedes unterschiedliche Objekt) gesetzt werden muss. Funktionen, die Objekte verwenden, geben normalerweise an, dass die Objekte normal sind.

Immer, wenn%% s%% Speicher vorhanden ist, füllt meine Funktion den Speicher mit malloc() -Werten. Eine Struktur, die nicht vollständig initialisiert ist, wird offensichtlich: Zählungen sind viel zu groß, Zeiger sind ungültig ( 0xDC ), und wenn ich mir die Struktur im Debugger anschaue, ist es offensichtlich, dass sie nicht initialisiert ist. Das ist viel besser, als Speicher beim Aufrufen von 0xDCDCDCDC zu füllen. (Natürlich ist die malloc() -Fill nur im Debug-Build; keine Notwendigkeit für den Release-Build, diese Zeit zu verschwenden.)

Wenn ich den Speicher frei mache, lösche ich den Zeiger. Wenn ich einen dummen Fehler habe, bei dem der Code versucht, einen Zeiger zu verwenden, nachdem sein Speicher freigegeben wurde, bekomme ich sofort eine Nullzeiger-Ausnahme, die mich direkt auf den Fehler hinweist. Meine Destruktorfunktionen nehmen keinen Zeiger auf ein Objekt, sie nehmen einen Zeiger auf einen Zeiger und klauen den Zeiger nach der Zerstörung des Objekts. Außerdem löschen Destruktoren ihre Objekte, bevor sie sie freigeben. Wenn also ein Teil des Codes eine Kopie eines Zeigers hat und versucht, ein Objekt zu verwenden, wird die Plausibilitätsprüfung assert sofort ausgelöst.

Valgrind sagt mir, ob irgendein Code das Ende eines Puffers schreibt. Wenn ich das nicht hätte, hätte ich nach den Enden der Puffer "kanarische" Werte gesetzt und die Plausibilitätsprüfung durchgeführt. Diese kanarischen Werte würden, wie die Signaturwerte, nur debug-build sein, so dass die Release-Version keinen Speicher aufgebläht hätte.

Ich habe eine Sammlung von Komponententests, und wenn ich größere Änderungen am Code vornehme, ist es sehr beruhigend, die Komponententests auszuführen und ein gewisses Vertrauen zu haben, dass ich die Dinge nicht schrecklich kaputt gemacht habe. Natürlich lasse ich die Unit-Tests sowohl auf der Debug-Version als auch auf der Release-Version laufen, so dass alle meine Behauptungen ihre Chance haben, Probleme zu finden.

All diese Struktur an Ort und Stelle zu bringen, war ein bisschen mehr Aufwand, aber es zahlt sich jeden Tag aus. Und ich bin ziemlich glücklich, wenn eine assert-Datei direkt auf einen Fehler hinweist und mich anzeigte, anstatt den Fehler im Debugger ausführen zu müssen. Auf lange Sicht ist es einfach weniger Arbeit, die Dinge immer sauber zu halten.

Schließlich muss ich sagen, dass ich die ungarische Notation mag. Ich habe vor ein paar Jahren bei Microsoft gearbeitet und wie Joel lernte ich Apps ungarisch und nicht die kaputte Variante. Es macht wirklich falschen Code falsch aussehen .

    
steveha 07.10.2009 06:23
quelle
13

Ebenso wichtig - wie Sie Sie sicherstellen, dass Ihre Dateien und Sockets geschlossen sind, werden Ihre Sperren freigegeben, yada yada. Speicher ist nicht die einzige Ressource, und mit GC verlieren Sie inhärent zuverlässige / rechtzeitige Zerstörung.

Weder GC noch Nicht-GC sind automatisch überlegen. Jeder hat Vorteile, jeder hat seinen Preis, und ein guter Programmierer sollte in der Lage sein, beides zu bewältigen.

Ich habe das in einer Antwort auf diese Frage .

    
Steve314 07.10.2009 05:54
quelle
3

Ich verwende C ++ seit 10 Jahren. Ich habe C, Perl, Lisp, Delphi, Visual Basic 6, C #, Java und verschiedene andere Sprachen benutzt, an die ich mich nicht mehr erinnern kann.

Die Antwort auf Ihre Frage ist einfach: Sie müssen wissen, was Sie tun , mehr als C # / Java. Das more than ist das, was Jeff Atwood in Bezug auf "Java-Schulen" hervorbringt >.

Die meisten Ihrer Fragen sind in gewisser Hinsicht unsinnig. Die "Probleme", die Sie ansprechen, sind einfach Fakten, wie Hardware wirklich funktioniert . Ich möchte Sie herausfordern, eine CPU & amp; RAM in VHDL / Verilog und sehen, wie Dinge wirklich funktionieren, auch wenn wirklich vereinfacht. Sie werden verstehen, dass der C # / Java-Weg ein Abstraktionstraining über Hardware ist.

Eine einfachere Herausforderung wäre es, ein elementares Betriebssystem für ein eingebettetes System vom ersten Einschalten an zu programmieren; es zeigt dir auch, was du wissen musst.

(Ich habe auch C # und Java geschrieben)

    
Paul Nathan 07.10.2009 16:14
quelle
3

Wir schreiben in C für eingebettete Systeme. Neben einigen Techniken, die in Programmiersprachen oder Umgebungen üblich sind, verwenden wir auch:

  • Ein statisches Analysetool (z. B. PC-Lint ).
  • Konformität mit MISRA-C (wird durch das statische Analysetool erzwungen).
  • Keine dynamische Speicherzuweisung.
Steve Melnikoff 07.10.2009 20:36
quelle
2

Andrew's Antwort ist eine gute, aber ich würde auch Disziplin in die Liste aufnehmen. Ich finde, dass man nach genügend Übung mit C ++ ein gutes Gefühl dafür bekommt, was sicher ist und was darauf wartet, dass die Velociraptoren kommen, um dich zu essen. Du tendierst dazu, einen Kodierstil zu entwickeln, der sich angenehm anfühlt, wenn du den sicheren Praktiken folgst und dich die heebie-jeebies fühlst, solltest du versuchen, einen intelligenten Zeiger auf einen rohen Zeiger zu setzen und ihn an etwas anderes weiterzugeben.

Ich mag es, wie ein Elektrowerkzeug in einem Geschäft zu denken. Es ist sicher genug, wenn Sie einmal gelernt haben, es richtig zu benutzen, und solange Sie sicherstellen, dass Sie immer alle Sicherheitsregeln befolgen. Wenn du denkst, dass du auf die Schutzbrille verzichten kannst, wirst du verletzt.

    
Boojum 07.10.2009 06:31
quelle
1

Ich habe sowohl C ++ als auch C # gemacht und ich sehe nicht den ganzen Hype über verwalteten Code.

Ach ja, es gibt einen Garbage Collector für Speicher, das ist hilfreich ... es sei denn, Sie verzichten auf die Verwendung von einfachen alten Zeigern in C ++, wenn Sie nur smart_pointers verwenden, dann haben Sie nicht so viele Probleme.

Aber dann würde ich gerne wissen ... schützt Ihr Müllsammler Sie vor:

  • Datenbankverbindungen offen halten?
  • Sperren von Dateien?
  • ...

Es gibt viel mehr Ressourcenverwaltung als Speicherverwaltung. Das Gute an C ++ ist, dass man schnell lernt, was Ressourcenmanagement und RAII bedeutet, damit es zu einem Reflex wird:

  • Wenn ich einen Zeiger haben möchte, möchte ich einen auto_ptr, einen shared_ptr oder einen weak_ptr
  • Wenn ich eine DB-Verbindung haben möchte, möchte ich ein Objekt 'Verbindung'
  • Wenn ich eine Datei öffne, möchte ich ein Objekt 'Datei'
  • ...

Wie bei Pufferüberläufen ist es nicht so, dass wir char * und size_t überall verwenden. Wir haben einige Dinge "String", "Iostream" und natürlich den bereits erwähnten Vektor :: bei der Methode, die uns von diesen Einschränkungen befreit.

Tested libraries (stl, boost) sind gut, benutzen sie und kommen zu mehr funktionalen Problemen.

    
Matthieu M. 07.10.2009 06:58
quelle
1

Neben vielen guten Tipps ist mein wichtigstes Tool DRY - Do not Repeat Yourself. Ich verbreite keinen fehleranfälligen Code (z. B. zur Behandlung von Speicherzuweisungen mit malloc () und free ()) über meine Codebasis. Ich habe genau einen einzigen Ort in meinem Code, wo malloc und free aufgerufen werden. Es ist in den Wrapper-Funktionen MemoryAlloc und MemoryFree.

Es gibt die gesamte Argumentprüfung und die anfängliche Fehlerbehandlung, die normalerweise als wiederholter Standardcode um den Aufruf von malloc herum gegeben wird. Darüber hinaus ermöglicht es alles mit der Notwendigkeit, nur einen Speicherort zu ändern, beginnend mit einfachen Debugging-Prüfungen wie z. B. das Zählen der erfolgreichen Aufrufe von malloc und free und überprüfen bei der Programmbeendigung, dass beide Zahlen gleich sind, bis zu allen Arten von erweiterten Sicherheitsüberprüfungen. p>

Manchmal, wenn ich hier eine Frage lese wie "Ich muss immer sicherstellen, dass strncpy die Zeichenfolge beendet, gibt es eine Alternative?"

%Vor% Nach einigen Tagen der Diskussion frage ich mich immer, ob die Kunst, wiederholte Funktionalität in Funktionen zu extrahieren, eine verlorene Kunst der höheren Programmierung ist, die nicht mehr in Programmiervorlesungen unterrichtet wird.

%Vor%

Das primäre Problem der Code-Duplizierung ist gelöst - jetzt wollen wir überlegen, ob strncpy wirklich das richtige Werkzeug für den Job ist. Performance? Vorzeitige Optimierung! Und einen einzigen Ort, um damit zu beginnen, nachdem es sich als der Flaschenhals erwiesen hat.

    
Secure 07.10.2009 09:45
quelle
0

C ++ hat alle Funktionen, die Sie erwähnen.

Es gibt Speicherverwaltung. Sie können Smart Pointer für eine sehr präzise Steuerung verwenden. Oder es gibt ein paar Garbage Collectors, obwohl sie nicht Teil des Standards sind (aber in den meisten Fällen sind Smart Pointer mehr als ausreichend).

C ++ ist eine stark typisierte Sprache. Genau wie C #.

Wir verwenden Puffer. Sie können festlegen, dass die überprüfte Version der Schnittstelle aktiviert ist. Aber wenn Sie wissen, dass es kein Problem gibt, dann können Sie die ungeprüfte Version der Schnittstelle verwenden.

Vergleichen Sie die Methode von () (aktiviert) mit operator [] (nicht markiert).

Ja, wir verwenden Unit Testing. Genau wie Sie in C # verwenden sollten.

Ja, wir sind vorsichtige Programmierer. Genau wie du in C # sein solltest. Der einzige Unterschied ist, dass die Fallstricke in den beiden Sprachen unterschiedlich sind.

    
Martin York 07.10.2009 05:58
quelle