Adventi naptár

Czirkos Zoltán · 2021.08.24.

scanf %n

A scanf()-nek van egy érdekes konverziója, amelyik igazából nem is konverzió. A %n hatására, amelynek megfelelő helyen egy int*-ot kell elhelyeznünk a paraméterlistán, a scanf() a megadott változóba írja, hogy addig a pontig hány karaktert dolgozott fel. Hatására konverzió nem történik, és karakter sem olvasódik be, csak a karakterek számát írja a változóba.

Ez hasznos lehet, ha egy sztringet nem tudunk egyszerre, egészében feldolgozni. Tegyük fel azt, hogy van egy fájlunk, amelyben a sorok formátuma lent látható. Tudjuk azt, hogy az esemény leírása egy vagy több szó is lehet. Ha több szóból áll az esemény, akkor idézőjelek "" között van megadva, ha csak egy szó, akkor pedig azok nélkül.

év-hónap-nap óra:perc eseményleírás hibakód
2010-11-30 12:45 egyszavas 404
2010-12-01 16:27 "tobb szobol allo" 201

A scanf()-fel tudunk szóközig olvasni (%s), és tudunk idézőjelig is (%[^"]). Csak azt nem tudjuk eldönteni előre, hogy melyik fog kelleni, és ezért nem lehet olyan formátumsztringet írni, amelyik az egész sort egyszerre képes lenne szétbontani.

Ilyenkor jön jól a %n. Beolvassuk a sztringet addig a pontig, amikor döntenünk kell, hogy idézőjelet látunk vagy nem. Egy %n-t rakva a formátumsztring végére a scanf() megmondja, a beolvasás hányadik karakternél állt meg; így a döntést meghozva (idézőjeles sztring vagy nem) onnan tudjuk folytatni.

#include <stdio.h >

int main(void) {
    /* ezeket dolgozzuk fel */
    char *sztringek[] = {
        "2010-11-30 12:45 egyszavas 404",
        "2010-12-01 16:27 \"tobb szobol allo\" 201"
    };

    /* ebbe */
    int ev, honap, nap, ora, perc;
    char szoveg[100];
    int szam;

    for (int j = 0; j < 2; j++) {
        char *feldolgoz = sztringek[j];

        /* beolvassuk az elejet, meg meg egy spacet.
         * az eredmenyek mennek a szamokba;
         * es a beolvasott karakterek szama i-be. */
        int i;
        sscanf(feldolgoz, "%d-%d-%d %d:%d %n",
               &ev, &honap, &nap, &ora, &perc, &i);

        /* es most a kovetkezo karakter (feldolgoz[i])
         * a kovetkezo sztring elso karaktere, ha nem idezojeles.
         * ha idezojeles, akkor pedig maga az idezojel.
         * ennek megfeleloen beolvassuk, vagy a kovetkezo
         * spaceig, vagy a "bezaro" idezojelig.
         * mindket esetben beolvassuk az utana kovetkezo
         * spacet is, es utana megint megjegyezzuk az innen
         * beolvasott karakterek szamat. */
        feldolgoz += i;
        if (*feldolgoz == '"')
            sscanf(feldolgoz, "\"%[^\"]\" %n", szoveg, &i);
        else
            sscanf(feldolgoz, "%s %n", szoveg, &i);

        /* es most jon a vege. */
        /* megin kihagyunk annyi karaktert, amennyit mar
         * beolvastunk az elobb. */
        feldolgoz += i;
        sscanf(feldolgoz, "%d", &szam);

        printf("%d-%d-%d %d:%d [%s] %d\n",
               ev, honap, nap, ora, perc, szoveg, szam);
    }

    return 0;

}

A printf() tudja ugyanezt, csak nem a beolvasott, hanem a %n-ig bezárólag kiírt karakterek számát adja meg egy cím szerint átadott int változóban Emiatt egyébként különösen fontos, hogy egy felhasználótól származó sztringet printf("%s", s); utasítással írjunk ki, ne a printf(s); sorral. A sztring ugyanis tartalmazhat % karaktert, amelyet a printf() értelmezni fog; ha %n-t tartalmaz, akkor pedig még írni is fog majd valahova a memóriába. Így a lenti, bal oldali kód hibás. Ha a felhasználó pl. azt írja be, hogy „%d”, akkor a printf() a paraméterei között keres egy számot is. A helyes változat jobb oldalt látható.

char s[100];
fgets(s, 100, stdin);
printf(s);
char s[100];
fgets(s, 100, stdin);
printf("%s", s);