C-Code ohne Warning

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

Aufgrund der Corona-Krise: Die Veröffentlichung von Stellenangeboten und -gesuchen ist bis 31.12.2020 kostenfrei. Das beinhaltet auch Angebote und Gesuche von und für Freischaffende und Selbstständige.

  • C-Code ohne Warning

    Hallo zusammen!
    Ich arbeite derzeit an einem C-Projekt mit XCode für OSX. Dabei entstand eine Codephrase zu der ich gerne ein paar Meinungen hätte.
    Man kann das Problem so zusammenfassen:

    int *a = NULL;
    {
    int b = 3;
    a = &b;
    }
    *a = 1;
    printf("%d\n", *a);

    Das kracht dann mal früher oder später (was eigentlich klar ist). Manchmal aber auch über längere Zeit leider nicht. Trotzdem liefert der sonst so übereifrige Compiler keine Warnung. Oder ich hab noch nicht den richtigen Schalter dazu gefunden. Jedenfalls hat mich der Mist jetzt eine ganzen Tag gekostet. Habe schon testweise alle Warnings auf YES aber es kommt keine Meldung. Kann mir vielleicht ein XCode-Spezialist sagen ob oder wie ich den Compiler eine Warnung abringen kann ? Man sollte doch meinen, dass es dazu was gibt. Es gibt da noch ein Feld für "Other Warning Flags". Vielleicht dort ?
    Danke und nächtliche Grüße
  • Aus echtem Interesse: Vor was sollte der Compiler warnen? Ich könnte mit eine Warnung a la "reference to unasigned address" o. ä. vorstellen, weil &b ausserhalb des Blocks nicht mehr auf allokierten Speicher verweist. Oder wegen der Zuweisung ausserhalb des Blocks, die diesen obsolet macht?

    Verzeiht, wenn ich als nicht-C-Profi Unsinn schreibe, ich würde es gerne verstehen...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Thomas schrieb:

    ...

    int *a = NULL;


    {
    int b = 3;
    a = &b;
    }


    *a = 1;
    printf("%d\n", *a);

    Das kracht dann mal früher oder später (was eigentlich klar ist). Manchmal aber auch über längere Zeit leider nicht. Trotzdem liefert der sonst so übereifrige Compiler keine Warnung. Oder ich hab noch nicht den richtigen Schalter dazu gefunden. Jedenfalls hat mich der Mist jetzt eine ganzen Tag gekostet. Habe schon testweise alle Warnings auf YES aber es kommt keine Meldung. Kann mir vielleicht ein XCode-Spezialist sagen ob oder wie ich den Compiler eine Warnung abringen kann ? Man sollte doch meinen, dass es dazu was gibt. Es gibt da noch ein Feld für "Other Warning Flags". Vielleicht dort ?
    ...
    @'Thomas: Weshalb erwartest Du hierfür einen Compilerhinweis und wie sollte dieser Deiner Meinung nach ausfallen? Der Code des Blockes wird grundsätzlich immer ausgeführt, Du könntest Dir also die {} sparen und selbst wenn nicht, durch die Zeile *a = 1 hat der Pointer grundsätzlich einen gültigen Wert. Kann es sein, dass Du evtl. nicht das ganze Problem in Deinem Beispielcode integriert hast?

    Nachtrag: Sollte der Pointer a referenziert werden bevor diesem ein gültigen Wert (Adresse) zugewiesen wird, könnte es zum Crash kommen. Doch hier greift Xcode ab Version 11.2 (bin mir nicht ganz sicher, könnte auch schon früher der Fall gewesen sein) ein. a wird nicht als direkte Sprungadresse angeben, sondern als Offset ausgehend vom Speicherbereich in dem das main() läuft, (schau Dir hierzu den kompilierten Code an) und somit hat a eine gültige Sprungadresse innerhalb eines reservierten Speicherbereichs. Ergo erkennt der Compiler keine Ungereimtheiten - somit erfolgt auch kein Compilerhinweis.

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von OSXDev () aus folgendem Grund: Nachtrag/Korektur

  • @macmoonshine: Der Analizer findets ok

    @mymattes: Sofern der Block eine Funktion ist warnt der Compiler schon jetzt, dass der zurückgegebene Pointer eine lokale Variable referenziert. Dieselbe Prüfung könnte eigentlich auf jeden Block angewendet werden, da sie inhaltsgleich ist.

    @OSXDev: Ich erwarte die selbe Warnung wie bei der Funktion (siehe vorigen Absatz). Der Pointer zeigt zwar innerhalb des Blocks auf einen gültigen Speicherbereich. Aber nach verlassen des Blocks wird dieser Bereich nicht mehr als belegt verwaltet. Wann und ob dieser Speicherbereich neu beschrieben wird hängt vom weiteren Verlauf des Programmes ab. Es kracht also nicht sofort an dieser Stelle sondern wenn später mal auf den von a referenzierten Bereich zugegriffen wird. Denn dort steht möglicherweise inzwischen etwas ganz anderes. In meinem Fall war das wieder ein Pointer, der so ins nirgendwo gezeigt hat. Egal wie mans dreht und wendet: nach verlassen des Blocks zeigt a IMMER auf einen nichtmehr gültigen Speicherbereich. Und der Compiler weiß das auch...
  • Thomas schrieb:

    @macmoonshine: Der Analizer findets ok

    @mymattes: Sofern der Block eine Funktion ist warnt der Compiler schon jetzt, dass der zurückgegebene Pointer eine lokale Variable referenziert. Dieselbe Prüfung könnte eigentlich auf jeden Block angewendet werden, da sie inhaltsgleich ist.

    @OSXDev: Ich erwarte die selbe Warnung wie bei der Funktion (siehe vorigen Absatz). Der Pointer zeigt zwar innerhalb des Blocks auf einen gültigen Speicherbereich. Aber nach verlassen des Blocks wird dieser Bereich nicht mehr als belegt verwaltet. Wann und ob dieser Speicherbereich neu beschrieben wird hängt vom weiteren Verlauf des Programmes ab. Es kracht also nicht sofort an dieser Stelle sondern wenn später mal auf den von a referenzierten Bereich zugegriffen wird. Denn dort steht möglicherweise inzwischen etwas ganz anderes. In meinem Fall war das wieder ein Pointer, der so ins nirgendwo gezeigt hat. Egal wie mans dreht und wendet: nach verlassen des Blocks zeigt a IMMER auf einen nichtmehr gültigen Speicherbereich. Und der Compiler weiß das auch...

    Ich wüßte nicht, dass standard C irgendwas von Blöcken kennt. Wie bereits weiter oben erwähnt sind die Klammern für den Code komplett unerheblich. Die Variable b hat den gleichen Gültikeitsbereich wie a. Von daher kann dieses Code Stück für sich alleine auch nicht crashen.
    Wenn dann sollte da wenn dann höchstens die Warnung kommen das diese Klammern obsolute sind aber vielliecht möchte man die ja haben um den Code übersichtlicher zu machen?

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Innerhalb des Blocks sprichst Du den Inhalt von a nicht den Pointer a an. a speichert zu diesem Zeitpunkt die Adresse von b nicht deren Inhalt. Da der Gültigkeitsbereich von a den Block umschließt würde ein Fehler nur entstehen wenn a vor der Zuweisung von *a = 1 als Pointer referenziert würde und an dieser Stelle greifen die BS-Mechanismen welche ab Xcode 11.2 (wie beschrieben) unterstützt werden. Schau Dir dies mal mit dem Debugger an.
  • Eigentlich möchte ich weniger darüber sprechen ob der Code buggy ist sondern wie man sowas automatisch detektieren kann.

    @Thallius: Auszug aus dem C-Lehrbuch:

    Block (block scope): Wird eine Variable in einem Anweisungsblock ({}) deklariert, reichen der Geltungsbereich und die Lebensdauer dieser Variablen vom Anfang des Anweisungsblocks bis zu seinem Ende.

    In meinem Beispiel ist also der Speicherbereich von b nach } wieder frei während a noch immer darauf zeigt.


    @OSXDev: Bin über den Debugger überhaupt erst auf die Ursache gekommen (XCode 11.4 + 11.5). So habe ich nämlich gesehen, dass der Speicherbereich auf den a zeigt nach Beendigung des Blocks durch eine temporäre Variable in einer nachfolgenden Funktion überschrieben wurde ohne dass diese Funktion überhaupt etwas von a wusste.


    Richtig, solange man nicht an der Stelle auf die a zeigt etwas bestimmtes erwartet gibt es auch kein Problem. Was im Endeffekt bei Pointern immer so ist. Insofern geb ich dir schon recht, dass der Code technisch machbar ist und läuft. Aber er ist nunmal gegenüber der C-Semantik nachvollziehbar bedenklich. D.h. der Compiler weiß an der Stelle über den ungültigen Wert von a Bescheid wenn er b freigibt. Da wärs auf jedenfall besser eine Warnung zu schmeißen oder wenn der Compiler beim Verlassen des Blocks a auf NULL setzen würde. Im Gegensatz zur üblichen Pointerproblematik, bei der der Compiler ja nie weiß ob die Adresse noch gültig ist, weiß er es hier eben schon. Darauf wollte ich hinaus.

    Was ist der von dir zitierte BS-Mechanismus ? Wofür steht die Abkürzung ?
  • Thomas schrieb:

    C-Quellcode

    1. //
    2. // main.c
    3. // test-block
    4. //
    5. // ...
    6. // ...
    7. //
    8. #include <stdio.h>
    9. int main(int argc, const char * argv[]) {
    10. // insert code here...
    11. printf("Hello, World!\n");
    12. int *a = NULL;
    13. {
    14. int b = 3;
    15. a = &b;
    16. }
    17. *a = 1;
    18. printf("%d\n", *a);
    19. return 0;
    20. }
    Alles anzeigen
    Ich glaube einen Block in einen Block habe ich in C noch nie verwendet. Dafür gibt's dann wieder Funktionen.

    Kann ich mir gut vorstellen, dass Du da etwas länger gesucht hast, bist Du das gefunden hast. Aber diesen Mist, wie Du es nennst, hast Du ja selber programmiert. Aber schade, dass der Xcode Analyzer einen darauf nicht hinweist.

    Es gibt auch noch andere Statische Code Analyzer. Hab mal schnell danach gesucht und cppcheck gefunden. Der spuckt folgendes aus:


    Quellcode

    1. ▶ cppcheck main.c
    2. Checking main.c ...
    3. main.c:19:6: error: Using pointer to local variable 'b' that is out of scope. [invalidLifetime]
    4. *a = 1;
    5. ^
    6. main.c:17:13: note: Address of variable taken here.
    7. a = &b;
    8. ^
    9. main.c:16:13: note: Variable created here.
    10. int b = 3;
    11. ^
    12. main.c:19:6: note: Using pointer to local variable 'b' that is out of scope.
    13. *a = 1;
    14. ^
    15. main.c:20:21: error: Using pointer to local variable 'b' that is out of scope. [invalidLifetime]
    16. printf("%d\n", *a);
    17. ^
    18. main.c:17:13: note: Address of variable taken here.
    19. a = &b;
    20. ^
    21. main.c:16:13: note: Variable created here.
    22. int b = 3;
    23. ^
    24. main.c:20:21: note: Using pointer to local variable 'b' that is out of scope.
    25. printf("%d\n", *a);
    26. ^
    Alles anzeigen
  • Thomas schrieb:

    ...
    @OSXDev: Bin über den Debugger überhaupt erst auf die Ursache gekommen (XCode 11.4 + 11.5). So habe ich nämlich gesehen, dass der Speicherbereich auf den a zeigt nach Beendigung des Blocks durch eine temporäre Variable in einer nachfolgenden Funktion überschrieben wurde ohne dass diese Funktion überhaupt etwas von a wusste.


    Richtig, solange man nicht an der Stelle auf die a zeigt etwas bestimmtes erwartet gibt es auch kein Problem. Was im Endeffekt bei Pointern immer so ist. Insofern geb ich dir schon recht, dass der Code technisch machbar ist und läuft. Aber er ist nunmal gegenüber der C-Semantik nachvollziehbar bedenklich. D.h. der Compiler weiß an der Stelle über den ungültigen Wert von a Bescheid wenn er b freigibt. Da wärs auf jedenfall besser eine Warnung zu schmeißen oder wenn der Compiler beim Verlassen des Blocks a auf NULL setzen würde. Im Gegensatz zur üblichen Pointerproblematik, bei der der Compiler ja nie weiß ob die Adresse noch gültig ist, weiß er es hier eben schon. Darauf wollte ich hinaus.

    Was ist der von dir zitierte BS-Mechanismus ? Wofür steht die Abkürzung ?
    @Thomas: Ob der Speicherbereich nach verlassen des Blocks freigegeben wird oder nicht, spielt keine Rolle, da Du a nach dem Block sofort mit einem gültigen Wert versorgst. An dieser Stelle kann der Static Analyzer keinen Fehler finden, da er nicht Zeile für Zeile analysiert sondern den vollständigen Code bewertet. Klar befindet sich a nach dem Beenden des Blocks und vor der neuen Zuweisung von a in einem undefiniertem Zustand, doch an dieser Stelle muss man wissen wie der Compiler die Übersetzung vornimmt um die Sicherheitsmechanismen des Betriebssystem (s. Doku) zu nutzen. Sicherlich wäre es hilfreich wenn der Static Analyzer zumindest einen Hinweis ausgeben würde, aber wie bereits oben genannt - bist Du derjenige welcher den Code programmiert.

    Nachtrag: Mal ein etwas anderer Ansatz, vielleicht hilft es Dir. developer.apple.com/documentation/xctest

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von OSXDev () aus folgendem Grund: Nachtrag

  • Thomas schrieb:

    @macmoonshine: Der Analizer findets ok
    Wenn Du statt eine lokale Variable (Stack) mit malloc dir Speicher (Heap) reservierst und diesen dann wieder frei gibst, dann meckert der Analyzer: "Use of a memory after it is freed".

    Ich kann mir auch gut vorstellen, dass das Verhalten von einem nested Scope von Compiler zu Compiler, Optimierungsstufe zu Optimierungsstufe unterschiedlich ist. Darum wird auch cppcheck meckern.