Speicherzugriffsfehler nach "system()"



  • Kaliber45 schrieb:

    Tatsächlich, so hat's funktioniert. Das ich nicht draufgekommen bin *schäm*.
    Aber dann frag ich mich, wann kann man denn unbestimmte größen von Arrays benutzten? Solche Fälle habe ich nämlich auch gesehen. Ich weiss ja nie, wie lang der Pfad wird, den ich dem Programm übergebe. Das war eigentlich 'ne Maßnahme um RAM zu sparen. Weisst du da was darüber. Also wann bzw. wann ich sowas machen darf?
    Danke für die schnelle Antwort.

    Diese Definition ohne explizite Größenangabe macht nur Sinn, wenn du in der Initialisierung bereits alle Array-Elemente aufzählen kannst (hat den Vorteil, daß du später die Initialisierungsliste erweitern kannst, wenn du es benötigst). Wenn du nicht weißt, wieviel Platz du benötigst, mußt du entweder eine Obergrenze vorgeben (und explizit in der Array-Definition angeben) oder dynamisch Speicher anfordern.



  • Die Zeiten, wo du dir "um ein paar Bytes zu sparen" Fehler in dein Programm einbaust, sollten doch wohl vorbei sein?
    Arrays haben eine statische Größe, entweder du definierst sie entsprechend sinnvoll groß oder du weichst auf dynamischen Speicher aus (malloc usw.).



  • Wutz schrieb:

    Die Zeiten, wo du dir "um ein paar Bytes zu sparen" Fehler in dein Programm einbaust, sollten doch wohl vorbei sein?
    Arrays haben eine statische Größe, entweder du definierst sie entsprechend sinnvoll groß oder du weichst auf dynamischen Speicher aus (malloc usw.).

    Das stimmt, dass man sich keine gedanken mehr machen muss, ob der Speicher ausreicht. Aber wenn man Qualitäten aufweisen möchte, sollte man schon auf solche Sachen achten. Das Programm ist jetzt zwar nix Weltbewegendes, doch als Informatikstudent muss ich das ja wohl lernen.
    Wie man das mit dem malloc() Hand habt werde ich mir dann mal anschauen und Danke euch beiden für den Hinweis und schnelle Antworten.



  • Kaliber45 schrieb:

    Das stimmt, dass man sich keine gedanken mehr machen muss, ob der Speicher ausreicht. Aber wenn man Qualitäten aufweisen möchte, sollte man schon auf solche Sachen achten. Das Programm ist jetzt zwar nix Weltbewegendes, doch als Informatikstudent muss ich das ja wohl lernen.
    Wie man das mit dem malloc() Hand habt werde ich mir dann mal anschauen und Danke euch beiden für den Hinweis und schnelle Antworten.

    Grunsätzlich ist eine Lösung auf dem Stack vorzuziehen. Auch wenn man mit malloc() vermeintlich Speicher spart, ist dem nicht so. Der Stack ist eh reserviert - da werden nur Pointer verändert. (Zudem ist der Stack wesentlich schneller und sicherer als ein malloc()/free() Gefrickel. Natürlich gibt es Situationen, in denen malloc()/free() vorzuziehen sind, aber das sollte man sich dann immer gut überlegen.)

    Edit:
    Hier könnte man z.B. ein hinreichend großes Array deklarieren und dann bevor man mit strcat() hantiert via strlen() überprüfen ob argv[1] eine sinnvolle Größe hat.



  • Diese Pauschalisierung ist Unsinn und falsch.
    Wenn du keine Zeiger magst und dein Gefrickel lieber auf dem Stack machst, ist das deine Sache.
    Bei Rekursion und Multithreading oder auch bei normalem Umgang ist es naiv darauf zu vertrauen, dass das System jeweils ausreichend Stack zur Verfügung hat zumal man die Stackspeicheranforderung im Gegensatz zu malloc nicht abprüfen kann. Dass Stack wesentlich schneller als Heap ist, ist ebenso Aberglaube, geeignete Speichermanager sind in professionellen Anwendungen mind. ebenso schnell.



  • Wutz schrieb:

    Dass Stack wesentlich schneller als Heap ist, ist ebenso Aberglaube, geeignete Speichermanager sind in professionellen Anwendungen mind. ebenso schnell.

    Blödsinn.



  • Du hast keine Ahnung.



  • Wutz schrieb:

    Du hast keine Ahnung.

    Stack allokation geht ueber ein add auf den Stackpointer. Das ist eine addition. Speicher am Heap muss ich suchen. Ich muss locken (zumindest ab und zu), muss den naechsten freien block finden, verliere etwas Speicher an overhead, muss den Speicher als belegt markieren und kassiere dabei vielleicht auch noch einen page fault,...

    Das ist einfach immer teurer als ein sub esp, 8
    vorallem wenn ich auch noch gleich reinschreiben kann und mir die naechste page fault sparen kann indem ich push eax mache...



  • Du scheinst bisher einen Speichermanager auch maximal nur von aussen gesehen zu haben, der macht auch nur ein add.
    Der Inline-Score eines optimierenden Compilers bei Funktionen ohne temporäre (Stack)Variablen ist auch eindeutig höher, Stacküberlaufprüfung mal ganz außen vor gelassen.



  • Wutz schrieb:

    Du scheinst bisher einen Speichermanager auch maximal nur von aussen gesehen zu haben, der macht auch nur ein add.

    Solche gibt es. Aber die müssen dann zur Strafe einen Garbage-Collector bemühen und zusätzlichen Verwaltungsaufwand betreiben, damit die Objekte verschiebbar sind. Da zahlt man dann doch drauf. Du wirst eine globalen oder threadlokale Variable haben fürs Hochzählen. Schon nicht billiger als der simple Stackpointer. Daß Du irgendwas schaffst, was schneller als der Stack ist, nehme ich Dir einfach nicht ab.

    Was Du von den Marketingfuzzis gelesen haben wirst, ist wohl, daß mit GC schneller sei als klassisches malloc/free. Das hat nichts mit dem Stack zu tun. Schonmal den produzioerten asm-Code angeschaut? Sicher nicht oft genug. 🙂

    Wutz schrieb:

    Der Inline-Score eines optimierenden Compilers bei Funktionen ohne temporäre (Stack)Variablen ist auch eindeutig höher,

    Bitte um Erklärung.

    Wutz schrieb:

    Stacküberlaufprüfung mal ganz außen vor gelassen.

    Macht die MMU kostenlos, Du Scherzkeks.



  • Reden wir hier von der Heap-Verwaltung in C bzw. C++ oder vom .NET Garbage Collector? Wenn du immer nur am Ende des Speichers ein Stück abschneidest, bekommst du sehr schnell große Lücken im Heap, die du auf diese Weise nicht mehr nutzen kannst. (und im Gegensatz zum Stack ist beim Heap auch nicht garantiert, daß er im LIFO-Verfahren wieder freigegeben wird)



  • Wutz schrieb:

    Du scheinst bisher einen Speichermanager auch maximal nur von aussen gesehen zu haben, der macht auch nur ein add.
    Der Inline-Score eines optimierenden Compilers bei Funktionen ohne temporäre (Stack)Variablen ist auch eindeutig höher, Stacküberlaufprüfung mal ganz außen vor gelassen.

    [citation needed]

    Stack-Allokation ist prima facie schon deshalb schneller, weil eine Heap-Allokation eine Stack-Allokation (des Zeigers) beinhaltet. Alles, was die Heap-Allokation (und -Deallokation) macht, ist extra bezahlt.

    Aus dem gleichen Grund halte ich es für ein Gerücht, dass Funktionen, die Stack-Variablen benutzen, seltener ingelinet würden. Wieviel Speicher die Funktion nachher auf dem Stack benutzt, ist für ihre Größe im Codesegment unerheblich, und der Speicher auf dem Stack wird unabhängig vom Inlining benutzt. Ein Compiler hat überhaupt keinen Grund, daraus ein Kriterium zu machen.

    Ansonsten hielte ich es für sinnvoll, wenn du dich in Zukunft über Begrifflichkeiten informierst, bevor du mit ihnen Buzzword-Bingo zu spielen versuchst. Ein Speichermanager ist keine magische Antwort auf alle Probleme, die man mit Speicher haben kann. Auch ist das Wort "Speichermanager" ohne Konkretisierung eine leere Hülle - es gibt eine ganze Reihe verschiedener Strategien zur Speicherverwaltung, die je nach Anwendungsfall mehr oder weniger sinnvoll sein können. Mit Aussagen wie

    Wutz schrieb:

    (...) der [Speichermanager] macht auch nur ein add.

    machst du dich lächerlich(er).



  • CStoll schrieb:

    Reden wir hier von der Heap-Verwaltung in C bzw. C++ oder vom .NET Garbage Collector? Wenn du immer nur am Ende des Speichers ein Stück abschneidest, bekommst du sehr schnell große Lücken im Heap, die du auf diese Weise nicht mehr nutzen kannst. (und im Gegensatz zum Stack ist beim Heap auch nicht garantiert, daß er im LIFO-Verfahren wieder freigegeben wird)

    Wovon andere reden interessiert mich wenig und wenn sie von Garbagecollection reden ist das eben ihre Sache nicht meine. Eine effiziente Speicherverwaltung fängt beim Programmdesign an und dabei ist es eben Unsinn, seine Nutzdaten (und darum ging es ausschließlich im Frage-Kontext; wer lesen kann ist klar im Vorteil) die üblicherweise auch deutlich mehr Speicher belegen als lokale Hilfsvariablen, zuvor in einen standardgemäß nichtfehlerzubehandelnden Stackspeicher abzulegen um ihn etwas später dann doch in irgendwelche passenderen und weniger flüchtigen Speicherbereiche kopieren zu müssen da das Sichtbarkeitsbereichsende erreicht ist. Und dafür gibt es eben Speichermanager, die sich dieser Aufgabe annehmen und im Gesamtresultat pauschal nicht langsamer dafür aber sicherer sind als eine Stackvariante.



  • Wutz schrieb:

    Eine effiziente Speicherverwaltung fängt beim Programmdesign an und dabei ist es eben Unsinn, seine Nutzdaten (und darum ging es ausschließlich im Frage-Kontext; wer lesen kann ist klar im Vorteil) die üblicherweise auch deutlich mehr Speicher belegen als lokale Hilfsvariablen, zuvor in einen standardgemäß nichtfehlerzubehandelnden Stackspeicher abzulegen um ihn etwas später dann doch in irgendwelche passenderen und weniger flüchtigen Speicherbereiche kopieren zu müssen da das Sichtbarkeitsbereichsende erreicht ist.

    Natürlich ist doppelte Arbeit machen langsamer als die Arbeit nur einmal zu machen.

    Und dafür gibt es eben Speichermanager, die sich dieser Aufgabe annehmen und im Gesamtresultat pauschal nicht langsamer dafür aber sicherer sind als eine Stackvariante.

    "Sicherer" hört sich ziemlich falsch an.
    Aber ja, ein Speicher Manager muss schon ziemlich schlecht sein wenn er bei der doppelten Arbeit nicht schneller als bei einfacher Arbeit.

    Aber darum gehts nicht.



  • @Wutz:
    Klar ist es sinnlos, die Daten erst auf den Stack zu packen, wenn man sie später sowieso in andere Bereiche verschieben will - da packe ich sie lieber gleich in den Bereich, wo sie weiterverarbeitet werden können. Aber darum geht es auch nicht, sondern um die Frage, ob man Daten, deren Überleben außerhalb des aktuellen Scope egal ist, besser auf dem Stack oder dem Heap unterbringen sollte.
    Im Umgang mit dem Stack weißt der Compiler, (a) wieviel Speicher eine Funktion benötigen wird und (b) in welcher Reihenfolge dieser Speicher wieder freigegeben wird (nach dem LIFO-Prinzip), also braucht er sich nicht um Speicher-Fragmentierung zu kümmern, sondern muß nur den Stack-Pointer erhöhen um zu sagen "der Bereich gehört jetzt mir". Bei dynamischer Speicherverwaltung weiß er im Vorfeld nichts über die Menge und die Freigabe-Reihenfolge, deswegen kann es passieren, daß im verfügbaren Speicherbereich Lücken auftreten, die nach Möglichkeit ausgenutzt werden sollten, wenn die nächste Anfrage kommt - da ist es mit dem einfachen Erhöhen eines Wertes nicht getan.
    (Bei Sprachen mit Garbage Collector sieht die Sache möglicherweise wieder anders aus - in .NET werden die Lücken geschlossen, indem die Objekte im Speicher herumgeschoben werden, da reicht dann tatsächlich eine einfache Zeiger-Addition um ein Objekt anzulegen. Umgekehrt hast du dort etwas höheren Aufwand beim Zerstören der Objekte)



  • CStoll schrieb:

    (Bei Sprachen mit Garbage Collector sieht die Sache möglicherweise wieder anders aus - in .NET werden die Lücken geschlossen, indem die Objekte im Speicher herumgeschoben werden, da reicht dann tatsächlich eine einfache Zeiger-Addition um ein Objekt anzulegen. Umgekehrt hast du dort etwas höheren Aufwand beim Zerstören der Objekte)

    Ganz so einfach ist es mit GCs nicht. Eigentlich ist es sogar ziemlich kompliziert.

    Es ist richtig, das bei Sprachen mit kompaktierendem GC die Erstallokation eines Objektes sehr fix ist (etwas mehr als eine Addition ist es schon, aber abgesehen vom Locking nicht allzu viel). Das ist allerdings nicht das Ende der Geschichte. Damit die Erstallokation so schnell geht, muss der Garbage-Collector den betreffenden Bereich des Heaps freihalten. Ich erkläre es mal anhand der Hotspot-JVM; das und ähnliche Verfahren sind allgemein üblich.

    Hotspot verwendet einen Generationsheap. Der Aufbau ist etwa so:

    | Young Generation               | Tenured                            |
    +------+------------+------------+------------------------------------+
    | Eden | Survivor 1 | Survivor 2 |                                    |
    +------+------------+------------+------------------------------------+
    

    Neue Objekte werden in Eden angelegt, Survivor 1 oder 2 beinhaltet relativ junge Objekte, die ein paar GCs überlebt haben, der jeweils andere Survivor ist leer, und Tenured ist der Teil des Heaps, in dem alte Objekte leben. Hier gibt es echte Probleme mit Fragmentierung, weswegen von Zeit zu Zeit (aber eher selten) kompaktiert wird. Die Kompaktierung ist sehr aufwändig.

    Wenn Eden vollläuft, wird der Garbage Collector auf die junge Generation losgelassen. Alle Objekte in Eden und dem belegten Survivor werden geprüft und die überlebenden in den leeren Survivor-Space umgelegt. Solche, die inzwischen alt genug sind, werden nach Tenured verschoben. Der Vorteil dieses Verfahrens ist, dass man sich im jungen Heap keine Gedanken um Fragmentierung machen muss.

    Der Nachteil ist, dass die Threads sich dauernd um den Heap prügeln. Der Garbage-Collector kann die Objekte nicht verschieben, wenn sie von anderen Threads gerade benutzt werden, also muss die virtuelle Maschine sehr vorsichtig sein, was das Scheduling betrifft. Ferner ist ein Teil des GC-Laufs ein Stop-The-World-Event, d.h. während eines Teils des GC-Laufs (hauptsächlich der Kopiererei) darf kein anderer Thread arbeiten, und wenn er damit durch ist, darf kein Thread gecachte Adressen benutzen. Das alles erfordert einen gewissen Aufwand, und in nebenläufigen Szenarien kann es unter bestimmten Umständen tödlich wirken.

    Das Verfahren funktioniert solange hinreichend schnell, wie bei einem GC-Lauf fast alle Objekte tot sind. Konkret bedeutet das: Wenn man im Zusammenhang mit einem GC hauptsächlich kurzlebige Objekte auf den Heap legt (größtenteils solche, die man in C auf den Stack gelegt hätte), hat man mit einem GC verkraftbare Performanceeinbußen bei der Allokation. Das ist auch das Hauptanliegen der ganzen Geschichte. Wenn ein Objekt etwas länger lebt, verursacht es durch die Kopiererei einiges an Mehraufwand, und wenn es langlebig genug ist, um in Tenured zu landen, kommt zusätzlich zu diesem Aufwand bei nachfolgenden GC-Läufen in Tenured eine Erschwerung der Kompaktierung hinzu - besonders kräftig am Ende seines Lebenszyklus, wenn es den Heap fragmentiert, obwohl das verhältnismäßig weniger ins Gewicht fällt, je länger der Lebenszyklus andauert.

    Was Stack-Allokation angeht, bleibt noch anzumerken, dass, sofern die Stackvariable, um die es geht, nicht die einzige in der Funktion ist, ihre Allokation überhaupt nichts kostet, weil jeder Compiler der Welt den Stackzeiger bei Eintritt in die Funktion einmal für alle Variablen erhöhen wird. Speichermanager brauchen auch amortisiert mindestens einen Funktionsaufruf plus was immer die Strategie, die der Speichermanager verfolgt, benötigt. Ergo sind Speichermanager pauschal langsamer als der Stack, auch wenn es mitunter gute Gründe für ihre Verwendung gibt.

    Die Argumentation "Jaaa, aber wenn man den Kram, den man auf den Stack packt, nachher doch in dynamischem Speicher braucht, dann ist ein Speichermanager schneller" kann ja wohl nicht ernst gemeint sein.

    Gleichwohl: cookys Argumentation ist nicht vollständig. Man kann sich hier wohl auf eine feste Obergröße festlegen, wenn man das tut, muss man aber aktiv darauf achten, diese auch einzuhalten. Nachträglich sei also snprintf erwähnt:

    char pathcom[1024]; /* Sollte ausreichen */
    
    if(snprintf(pathcom, 1024, "ls -l %s > ../lists/indir.txt", argv[1]) < 1024)) {
      system(pathcom);
    } else {
      /* Eingabe zu lang */
    }
    

    Abhängig davon, was er nachher damit machen will, wäre es aber womöglich sinnvoller, mit opendir() etc. zu arbeiten.


Anmelden zum Antworten