fscanf und Strings am Zeilenende



  • Hoi ! 😉

    Mein Code ist zwar C++, aber mit Formatstrings kennen sich hier wohl mehr Leute aus. 😉

    Ich fang mal an...

    Ich möchte Dateien dieser Art parsen:

    ( cat /proc/[PID]/maps )

    00400000-004e0000 r-xp 00000000 fc:03 261125 /bin/bash
    006df000-006e0000 r--p 000df000 fc:03 261125 /bin/bash
    006e0000-006e9000 rw-p 000e0000 fc:03 261125 /bin/bash
    006e9000-006ef000 rw-p 00000000 00:00 0
    00db0000-010ed000 rw-p 00000000 00:00 0 [heap]
    7f99b8fef000-7f99b8ffb000 r-xp 00000000 fc:03 134586 /lib/x86_64-linux-gnu/libnss_files-2.13.so
    7f99b8ffb000-7f99b91fa000 ---p 0000c000 fc:03 134586 /lib/x86_64-linux-gnu/libnss_files-2.13.so
    7f99b91fa000-7f99b91fb000 r--p 0000b000 fc:03 134586 /lib/x86_64-linux-gnu/libnss_files-2.13.so
    7f99b91fb000-7f99b91fc000 rw-p 0000c000 fc:03 134586 /lib/x86_64-linux-gnu/libnss_files-2.13.so
    7f99b91fc000-7f99b9207000 r-xp 00000000 fc:03 134590 /lib/x86_64-linux-gnu/libnss_nis-2.13.so
    7f99b9207000-7f99b9406000 ---p 0000b000 fc:03 134590 /lib/x86_64-linux-gnu/libnss_nis-2.13.so
    7f99b9406000-7f99b9407000 r--p 0000a000 fc:03 134590 /lib/x86_64-linux-gnu/libnss_nis-2.13.so
    7f99b9407000-7f99b9408000 rw-p 0000b000 fc:03 134590 /lib/x86_64-linux-gnu/libnss_nis-2.13.so
    7f99b9408000-7f99b941f000 r-xp 00000000 fc:03 134580 /lib/x86_64-linux-gnu/libnsl-2.13.so
    7f99b941f000-7f99b961e000 ---p 00017000 fc:03 134580 /lib/x86_64-linux-gnu/libnsl-2.13.so
    7f99b961e000-7f99b961f000 r--p 00016000 fc:03 134580 /lib/x86_64-linux-gnu/libnsl-2.13.so
    7f99b961f000-7f99b9620000 rw-p 00017000 fc:03 134580 /lib/x86_64-linux-gnu/libnsl-2.13.so
    7f99b9620000-7f99b9622000 rw-p 00000000 00:00 0
    7f99b9622000-7f99b962a000 r-xp 00000000 fc:03 134582 /lib/x86_64-linux-gnu/libnss_compat-2.13.so
    7f99b962a000-7f99b9829000 ---p 00008000 fc:03 134582 /lib/x86_64-linux-gnu/libnss_compat-2.13.so
    7f99b9829000-7f99b982a000 r--p 00007000 fc:03 134582 /lib/x86_64-linux-gnu/libnss_compat-2.13.so
    7f99b982a000-7f99b982b000 rw-p 00008000 fc:03 134582 /lib/x86_64-linux-gnu/libnss_compat-2.13.so
    7f99b982b000-7f99b9c35000 r--p 00000000 fc:03 6519 /usr/lib/locale/locale-archive
    7f99b9c35000-7f99b9dbf000 r-xp 00000000 fc:03 134540 /lib/x86_64-linux-gnu/libc-2.13.so
    7f99b9dbf000-7f99b9fbe000 ---p 0018a000 fc:03 134540 /lib/x86_64-linux-gnu/libc-2.13.so
    7f99b9fbe000-7f99b9fc2000 r--p 00189000 fc:03 134540 /lib/x86_64-linux-gnu/libc-2.13.so
    7f99b9fc2000-7f99b9fc3000 rw-p 0018d000 fc:03 134540 /lib/x86_64-linux-gnu/libc-2.13.so
    7f99b9fc3000-7f99b9fc9000 rw-p 00000000 00:00 0
    7f99b9fc9000-7f99b9fcb000 r-xp 00000000 fc:03 134550 /lib/x86_64-linux-gnu/libdl-2.13.so
    7f99b9fcb000-7f99ba1cb000 ---p 00002000 fc:03 134550 /lib/x86_64-linux-gnu/libdl-2.13.so
    7f99ba1cb000-7f99ba1cc000 r--p 00002000 fc:03 134550 /lib/x86_64-linux-gnu/libdl-2.13.so
    7f99ba1cc000-7f99ba1cd000 rw-p 00003000 fc:03 134550 /lib/x86_64-linux-gnu/libdl-2.13.so
    7f99ba1cd000-7f99ba20d000 r-xp 00000000 fc:03 130623 /lib/libncurses.so.5.7
    7f99ba20d000-7f99ba40c000 ---p 00040000 fc:03 130623 /lib/libncurses.so.5.7
    7f99ba40c000-7f99ba410000 r--p 0003f000 fc:03 130623 /lib/libncurses.so.5.7
    7f99ba410000-7f99ba411000 rw-p 00043000 fc:03 130623 /lib/libncurses.so.5.7
    7f99ba411000-7f99ba432000 r-xp 00000000 fc:03 134527 /lib/x86_64-linux-gnu/ld-2.13.so
    7f99ba608000-7f99ba60b000 rw-p 00000000 00:00 0
    7f99ba623000-7f99ba62f000 r--p 00000000 fc:03 411993 /usr/share/locale-langpack/de/LC_MESSAGES/bash.mo
    7f99ba62f000-7f99ba631000 rw-p 00000000 00:00 0
    7f99ba631000-7f99ba632000 r--p 00020000 fc:03 134527 /lib/x86_64-linux-gnu/ld-2.13.so
    7f99ba632000-7f99ba634000 rw-p 00021000 fc:03 134527 /lib/x86_64-linux-gnu/ld-2.13.so
    7fff36e4a000-7fff36e6b000 rw-p 00000000 00:00 0 [stack]
    7fff36f8f000-7fff36f90000 r-xp 00000000 00:00 0 [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

    Das wäre der Code, mit dem ich das möchte (m_maps ist ein geöffnetes Handle(FILE*)):

    void MemoryRegionIterator::get()
    {
      std::array<char, 2048> pathBuffer;
      fscanf(m_maps, "%lx-%lx %4s %x %hx:%hx %u %2048[^\n]",
        &m_current.m_start, &m_current.m_end, &m_current.m_perms[0],
        &m_current.m_offset, &m_current.m_devMajor, &m_current.m_devMinor,
        &m_current.m_inode, &pathBuffer[0]);
    
      // Buffer gets reused.
      m_current.m_path.assign(&pathBuffer[0]);
    }
    

    Jetzt ist das Problem dass das zwar gut geht wenn ein Pfad/Beschreiber vorhanden ist (jeweils am Zeilenende), wenn allerdings das Feld leer ist, dann wird die komplette nächste Zeile als String eingelesen, was natürlich doof ist. Wie müsste ich den Formatstring abändern ?

    Dann, wie kann ich einzelne Zeichen in einen Charbuffer extrahieren, ohne dass eine Nullterminierung angehängt wird?

    Danke schon mal!
    Grüße,
    Ethon



  • Mach erst ein fgets() und auf die gelesene Zeile ein sscanf()

    Du kannst das Array doch auch direkt ansprechen

    char feld[100];
    feld[0]='H';
    feld[1]='a';
    ...
    


  • Immer wieder dasselbe.
    fscanf ist diffizil zu handhaben und nichts für Anfänger, wie man an dir und vielen deiner diesbezüglichen Vorgänger sieht.
    Verwende fgets und sscanf für derlei Aufgaben und einen Rückgabewert sollte deine Methode auch liefern etwa:

    int MemoryRegionIterator::get()
    {
      char zeile[3000],tmp[3000];
      if( !fgets(zeile,3000,m_maps) ) return -1;
      if( 8!=sscanf(zeile,"%lx-%lx%4s%x%hx:%hx%u%2048[^\n]",
        &m_current.m_start, &m_current.m_end, &m_current.m_perms[0],
        &m_current.m_offset, &m_current.m_devMajor, &m_current.m_devMinor,
        &m_current.m_inode, tmp) ) return 0;
    
      m_current.m_path.assign(tmp);
      return 1;
    }
    

    und im aufrufenden Kontext dann in etwa

    int r;
    while( (r=instanz->get())!=-1 )
      if(r) cout << "Zeile gelesen und übernommen" << endl;
    


  • Diese "7f99b9fc9000-7f99b9fcb000" sehen auch nach 48-Bit aus. Ein normales long reicht da nicht mehr aus. (wegen %lx)



  • Mach erst ein fgets() und auf die gelesene Zeile ein sscanf()
    

    Jop, das hatte ich davor, nur hab ich mir vorübergehend eingebildet, dass auf dem Weg eine unnötige Kopie angelegt wird ... was ja nicht stimmt. 😉
    Deswegen hat sich der Sinn von fscanf() damit sowieso für mich erledigt.
    Danke!

    Diese "7f99b9fc9000-7f99b9fcb000" sehen auch nach 48-Bit aus. Ein normales longreicht da nicht mehr aus. (wegen %lx)

    Das sind Speicheraddressen (64bit im Beispiel) und da mein Code unter Linux läuft, kann ein unsigned long garantiert eine Speicheraddresse speichern.

    fscanf ist diffizil zu handhaben und nichts für Anfänger

    Du bist aber auch ein Anfänger, sonst hättest du doch sicher eine Lösung mit fscanf() vorgeschlagen, oder? 😉



  • Ethon schrieb:

    Du bist aber auch ein Anfänger, sonst hättest du doch sicher eine Lösung mit fscanf() vorgeschlagen, oder? 😉

    Vielleicht möchte er einfach, dass Du seinen Lösungsvorschlag auch verstehst?



  • Da der ursprüngliche Code augenscheinlich C++ ist (std::array gibt's in C nicht, und die Variablen sehen verflucht nach Membern aus), wäre das alles bedeutend einfacher mit std::ifstream zu haben.

    Ansonsten könnte man natürlich schlicht fscanf für die fest vorhandenen Bestandteile benutzen und ein fgets hinterherschieben.



  • Da der ursprüngliche Code augenscheinlich C++ ist (std::array gibt's in C nicht, und die Variablen sehen verflucht nach Membern aus), wäre das alles bedeutend einfacher mit std::ifstream zu haben.

    Das Problem ist dass Iostreams nichtkopierbar sind und Move-Semantiken dafür zumindestens mit dem gcc 4.6 broken sind.
    Aber wirklich schöner wars mit Streams auch nicht, da man desöfteren ignore() braucht.

    Ansonsten könnte man natürlich schlicht fscanf für die fest vorhandenen Bestandteile benutzen und ein fgets hinterherschieben.

    Wäre eine Idee, aber da es wohl keine Performance-Vorteile gibt fscanf() zu nutzen anstatt fgets()+sscanf() werde ich mir das wohl sparen.
    Die Funktion ist im Moment ein Bottleneck, da sie viele tausend bis zehntausende Mal pro Sekunde aufgerufen wird.

    Vielleicht möchte er einfach, dass Du seinen Lösungsvorschlag auch verstehst?

    Ist ja nett dass er mir Code postet, aber der unterscheidet sich nicht groß von dem Code, den ich bereits hatte und zu fscanf() umschreiben wollte. Er prüft höchstens noch den Rückgabewert, was aber ein redundanter Check wäre da diese Art Datei komplett virtuell ist und das Format hartcodiert ist.

    Es hätte mich halt interessiert, ob das Problem rein durch eine Änderung des Formatstrings lösbar wäre. Denn Formatstrings in C sind irgendwie eine Wissenschaft für sich, zumindestens findet man 50% der Features nicht in gängigen Dokumentationen und darüber hinaus schippt jeder Hersteller noch Extensions hinterher...
    Deswegen würde es mich auch interessieren warum ich hier als "Anfänger" tituliert werde, ich bin vlt kein Guru, aber es reicht um meine Brötchen zu verdienen. 🙄



  • Ethon schrieb:

    Da der ursprüngliche Code augenscheinlich C++ ist (std::array gibt's in C nicht, und die Variablen sehen verflucht nach Membern aus), wäre das alles bedeutend einfacher mit std::ifstream zu haben.

    Das Problem ist dass Iostreams nichtkopierbar sind und Move-Semantiken dafür zumindestens mit dem gcc 4.6 broken sind.
    Aber wirklich schöner wars mit Streams auch nicht, da man desöfteren ignore() braucht.

    Wer kommt denn auch auf die Idee, einen Stream zu kopieren? Die FILE-Objekte, die die C stdio verwaltet, würde doch auch niemand ernsthaft kopieren wollen.

    PS: Ich würde eventuell noch die Kombination getline()+sscanf() anbieten 😉

    Wäre eine Idee, aber da es wohl keine Performance-Vorteile gibt fscanf() zu nutzen anstatt fgets()+sscanf() werde ich mir das wohl sparen.
    Die Funktion ist im Moment ein Bottleneck, da sie viele tausend bis zehntausende Mal pro Sekunde aufgerufen wird.

    Welche Funktion wird so oft aufgerufen? Die komplette Datei-Einlesen-Funktion oder das (f/s)scanf?



  • Wer kommt denn auch auf die Idee, einen Stream zu kopieren? Die FILE-Objekte, die die C stdio verwaltet, würde doch auch niemand ernsthaft kopieren wollen.

    Naja, ich werkle an einer Iteratorklasse und STL-Algorithmen kopieren Iteratoren ja prinzipiell.
    Deswegen muss es schon kopierbar sein, auch wenn natürlich eine flat Copy reicht.

    Welche Funktion wird so oft aufgerufen? Die komplette Datei-Einlesen-Funktion oder das (f/s)scanf?
    

    Ich hol mal aus 🙂
    Ich benutze die Liste aller Speicherregionen, um zu bestimmen ob eine Addresse beschreib-/lesbar bzw gültig ist.
    Dazu hab ich die Iteratorklasse gebaut, die im Konstruktor die entsprechende virtuelle Datei öffnet und bei jedem inkrementieren eine neue Zeile zieht und parst.
    Und manche Prozesse kommen da auf 500-1000 Zeilen/Regionen.
    Im Moment checke ich bei jeder Operation in meiner Library, was schon auf 1000 Operationen pro Sekunde kommen kann.



  • Ethon schrieb:

    Wer kommt denn auch auf die Idee, einen Stream zu kopieren? Die FILE-Objekte, die die C stdio verwaltet, würde doch auch niemand ernsthaft kopieren wollen.

    Naja, ich werkle an einer Iteratorklasse und STL-Algorithmen kopieren Iteratoren ja prinzipiell.
    Deswegen muss es schon kopierbar sein, auch wenn natürlich eine flat Copy reicht.

    Dann mußt du nicht den Stream kopieren, sondern nur die Stream-Iteratoren (wenn du ein Problem damit hast, daß Stream-Iteratoren nicht parallel lesen können, in Boost::spirit gab es afaik Multipass-Iteratoren für solche Zwecke).

    Ich hol mal aus 🙂
    Ich benutze die Liste aller Speicherregionen, um zu bestimmen ob eine Addresse beschreib-/lesbar bzw gültig ist.
    Dazu hab ich die Iteratorklasse gebaut, die im Konstruktor die entsprechende virtuelle Datei öffnet und bei jedem inkrementieren eine neue Zeile zieht und parst.
    Und manche Prozesse kommen da auf 500-1000 Zeilen/Regionen.
    Im Moment checke ich bei jeder Operation in meiner Library, was schon auf 1000 Operationen pro Sekunde kommen kann.

    Wie oft ändert sich denn diese Liste durchschnittlich? Wenn die halbwegs stabil bleibt, könntest du sie einmal einlesen und danach aus dem RAM auslesen.

    PS: Was soll deine Liste eigentlich darstellen? Und wozu mußt du in der Sekunde 1000 mal nach freien/gültigen Speicherbereichen suchen?



  • Ethon schrieb:

    Das sind Speicheraddressen (64bit im Beispiel) und da mein Code unter Linux läuft, kann ein unsigned long garantiert eine Speicheraddresse speichern.

    Sicher?
    Hast du schon mal printf(" long: %u\n", sizeof(long)); gemacht?



  • DirkB schrieb:

    Ethon schrieb:

    Das sind Speicheraddressen (64bit im Beispiel) und da mein Code unter Linux läuft, kann ein unsigned long garantiert eine Speicheraddresse speichern.

    Sicher?
    Hast du schon mal printf(" long: %u\n", sizeof(long)); gemacht?

    Das einlesen funktioniert ja.
    Außerdem, das Stichwort ist LP64: http://en.wikipedia.org/wiki/64-bit#Specific_C-language_data_models (konträr zu LLP64) 🙂

    Dann mußt du nicht den Stream kopieren, sondern nur die Stream-Iteratoren (wenn du ein Problem damit hast, daß Stream-Iteratoren nicht parallel lesen können, in Boost::spirit gab es afaik Multipass-Iteratoren für solche Zwecke).

    Wenn ich einen Stream-Iterator habe, müsste ich doch trotzdem irgendwo den Stream haben und steh vor dem selben Problem was Kopieren angeht, oder?

    Wie oft ändert sich denn diese Liste durchschnittlich? Wenn die halbwegs stabil bleibt, könntest du sie einmal einlesen und danach aus dem RAM auslesen.

    PS: Was soll deine Liste eigentlich darstellen? Und wozu mußt du in der Sekunde 1000 mal nach freien/gültigen Speicherbereichen suchen?

    Die Textdatei ist komplett virtuell und lässt sich über die FileAPIs öffnen, wobei dann on-the-fly vom Linux-Kernel Informationen über alle gemappten SpeicherRegionen erzeugt werden.
    Zum Beispiel werden für ein Modul mindestens 3 Regionen gemappt, nämlich für Code, Data und Readonly-Data + X.
    Eine Addresse, die sich nicht innerhalb einer Region befindet, ist demnach ungültig und der Versuch sie zu lesen/beschreiben würde in einem SegFault/AccessViolation resultieren.

    Ich bastle eine Memoryhacking-Bibliothek für Linux, mit der man eben im Speicher von fremden Prozessen rumpfuschen kann um beispielsweise Cheats zu schreiben. 😉
    Und da ist es schon nett wenn man Addressen prüfen kann, auch wenn natürlich ein Cachen vollkommen ausreichen würde.
    Ich habe den Check zur Zeit auch nur in einem Assert, dh. wenn ich auf Release umstelle, erledigt sich das Ganze. Aber trotzdem will ich mir die Option offen halten NICHT zu cachen, von daher möchte ich hier die Performance schon optimieren. 🙂

    Damit du dir besser etwas darunter vorstellen kannst, so sieht das Interface aus: https://github.com/Ethon/Ethonmem/blob/master/include/Ethon/MemoryRegions.hpp

    Aber jetzt hab ich ein ganz neues Problem: Wenn mein Iterator kopiert wird, dann wird logischerweise 2x der Destruktor aufgerufen und mein Programm fliegt mir um die Ohren.

    Bin gerade recht verzweifelt weil mir keine Lösung einfällt außer
    1. Einen Pointer auf das FILE* rumzureichen und das den FILE* nach dem schließen zu nullen um da etwas prüfen zu können.
    2. Einen Pointer auf einen std::ifstream, bei dem 2x close auch egal sind.
    3. Einen Wrapper um die nativen Filedeskriptoren zu bauen, da ich dann dup() nutzen kann um richtig zu kopieren.
    4. Die ganze File bereits im Konstruktor einlesen und nach jedem Inkrementieren eine Zeile aus dem Buffer zu poppen. (Gefällt mir persönlich am Schlechtesten).

    Übersehe ich da ne elegantere Methode?
    Edit: Schreit das nicht gerade nach nem shared_ptr mit custom deleter?

    Danke!
    Grüße,
    Ethon

    (Ach ja, vlt sollte man das verschieben bevor der C-Rage losgeht^^⁾



  • Ethon schrieb:

    Dann mußt du nicht den Stream kopieren, sondern nur die Stream-Iteratoren (wenn du ein Problem damit hast, daß Stream-Iteratoren nicht parallel lesen können, in Boost::spirit gab es afaik Multipass-Iteratoren für solche Zwecke).

    Wenn ich einen Stream-Iterator habe, müsste ich doch trotzdem irgendwo den Stream haben und steh vor dem selben Problem was Kopieren angeht, oder?

    Stream-Iteratoren sind etwas anderes als der darunterliegende Stream. Du würdest doch auch nicht auf die Idee kommen, einen größeren vector<> jedes Mal zu kopieren, wenn du darin einen Wert suchst. Du kannst (theoretisch) beliebige Iteratoren auf den Stream anlegen. Der Stream existiert unabhängig davon und ist aus selber dafür zuständig, seine Resourcen aufzuräumen.
    Einziges Problem könnte die etwas schwache Kopiersemantik von Input-Iteratoren sein, aber deswegen gibt es die erwähnten Multipass-Iteratoren.


Log in to reply