Rodzina printf scanf - MarekBykowski/readme GitHub Wiki
Rodzina printf / scanf — formatowane I/O w C
sprintfpisze do buf tekst sformatowany ze zmiennychsscanfbierze sformatowane dane z buf i zapisuje je do zmiennych.sprintfczyta zmienne -> podajesz wartośćsscanfzapisuje do zmiennych -> podajesz adres (&a)
Ten sam string formatujący opisuje ten sam kształt danych — raz do zapisu, raz do odczytu.
char buf[64];
int a = 10, b = 20;
sprintf(buf, "%d %d", a, b); /* zmienne → buf : buf = "10 20" */
sscanf (buf, "%d %d", &a, &b); /* buf → zmienne: a=10, b=20 */
Asymetria wskaźników: sprintf tylko czyta zmienne → podajesz wartość.
sscanf musi zapisać wynik → podajesz adres (&).
Wyjście — rodzina printf
Nazwa koduje: dokąd trafia wynik + czy wariant bierze va_list.
| Funkcja | Cel wyjścia | Uwagi |
|---|---|---|
printf |
stdout |
|
fprintf |
FILE * |
plik, stderr, … |
sprintf |
char * bufor |
bez limitu — niebezpieczne, łatwo przepełnić |
snprintf |
bufor z limitem size |
return = długość pełnego napisu (może być >= size) |
dprintf |
deskryptor int fd |
POSIX, nie FILE * |
asprintf / vasprintf |
bufor mallociem |
GNU/BSD, sam alokuje; potem free |
vprintf, vfprintf, vsprintf, vsnprintf, vdprintf |
jw. | biorą va_list zamiast ... |
Wejście — rodzina scanf
| Funkcja | Źródło | Uwagi |
|---|---|---|
scanf |
stdin |
|
fscanf |
FILE * |
|
sscanf |
const char * |
parsowanie gotowego stringa — bardzo częste |
vscanf, vfscanf, vsscanf |
jw. | biorą va_list |
scanf — pułapki
- Przekazuj adresy (
&a), bo funkcja zapisuje do zmiennych.scanf("%d", a)skompiluje się (może z ostrzeżeniem) i wywali w runtime. %sjest już adresem — nazwa tablicy rozpada się do wskaźnika:sscanf(buf, "%s", slowo)bez&.- Sprawdzaj return = liczba dopasowanych pól, nie 0/1 sukcesu.
Return mniejszy niż liczba
%→ część zmiennych nietknięta (śmieci).
if (sscanf(buf, "%d %d", &a, &b) != 2) {
/* wejście nie pasowało do szablonu */
}
%sbez szerokości przepełnia cel jaksprintf. Ograniczaj:sscanf(buf, "%31s", cel)dlachar cel[32]— 31 znaków + miejsce na\0.
Kernelowe warianty (lib/vsprintf.c)
Kernel nie linkuje glibc — ma własne implementacje. sscanf, snprintf,
sprintf istnieją i nazywają się tak samo. Po stronie zapisu dochodzi
wariant, którego w libc nie ma: scnprintf.
snprintf vs scnprintf — różnica w return
| Funkcja | Return przy obcięciu |
|---|---|
snprintf (libc) |
długość pełnego napisu — może przekroczyć size |
scnprintf (kernel) |
realnie zapisane bajty, maks. size - 1 |
Implementacja to cienka nakładka na vsnprintf:
int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
int i;
if (unlikely(!size))
return 0;
i = vsnprintf(buf, size, fmt, args);
if (likely(i < size))
return i;
return size - 1; /* przycięcie do size - 1 */
}
Idiom „buduj string w pętli"
To główny powód istnienia scnprintf. Bo return nigdy nie przekracza
size - 1, pos nasyca się na buf_size - 1, a buf_size - pos
(typ size_t, bez znaku) nigdy się nie zawija w gigantyczną liczbę.
char buf[128];
size_t buf_size = sizeof(buf);
size_t pos = 0;
int i;
for (i = 0; i < 10; i++)
pos += scnprintf(buf + pos, buf_size - pos, "%d ", liczby[i]);
/* buf[pos] to już '\0' — scnprintf go tam postawił */
Z snprintf return może być > size → pos przeskakuje buf_size →
buf_size - pos zawija się do ~2^64 → następne wywołanie rozwala bufor.
scnprintfchroni pamięć, nie treść. Gwarantuje, że nie wyjdziesz poza bufor, ale ostatni token może wylądować obcięty (np."10"zamiast"100"). O sensowność treści przy braku miejsca musisz zadbać sam (jawny guard przed pętlą, jak wcpufreq).
Nowsze API sysfs
W handlerach show zamiast scnprintf zaleca się dziś sysfs_emit() /
sysfs_emit_at() — cienkie wrappery robiące to samo, ale zakładające
PAGE_SIZE i liczące offset względem początku strony (trudniej się pomylić).
scnprintf zostaje do reszty: proc, debugfs, budowanie komunikatów.
Terminator '\0' — rozmiar i rola
'\0'to bajt o wartości 0, zajmuje 1 bajt jak każdy znak. To nie to samo co'0'(cyfra, wartość 48).- Rola magiczna (koniec stringa), rozmiar zwyczajny.
char a[] = {'a', 'b'}; /* sizeof == 2, BEZ nulla — NIE string */
char b[] = {'a', 'b', '\0'}; /* sizeof == 3, terminator ręcznie */
char c[] = "ab"; /* sizeof == 3, kompilator dokłada '\0' */
b i c są identyczne w pamięci. Literał "ab" to skrót — kompilator
zawsze dokleja \0, dlatego napis 2-znakowy zajmuje 3 bajty.
sizeofvsstrlen: dla"ab"→sizeof == 3(pamięć, z terminatorem),strlen == 2(treść, bez terminatora).
Dodanie \0 do istniejącej tablicy
Nie „powiększasz" tablicy — jej rozmiar jest stały. Albo jest zapas miejsca
i wpisujesz \0 pod właściwy indeks, albo kopiujesz do większego bufora.
char t[3];
memcpy(t, (char[]){'a','b'}, 2);
t[2] = '\0'; /* teraz poprawny string "ab" */
Niuans inicjalizacji: char t[10] = {'a','b'}; — podanie choć jednego
inicjalizatora w {} zeruje resztę, więc t[2] już jest \0.
Ta gwarancja nie obowiązuje przy wypełnianiu w pętli / memcpy bez {}.
Przy scnprintf terminatora nie dokładasz ręcznie — funkcja stawia go sama
i zostawia pos wskazujące tuż za treścią (czyli na terminatorze), więc
kolejny zapis nadpisuje go i buduje ciągły string bez dziur.