W swojej książce The Practice of Programming (która jest warta przeczytania), Kernighan i Pike omawiają ten problem i rozwiązują go, używając snprintf()
do stworzenia łańcucha z odpowiednim rozmiarem bufora do przekazania do scanf()
rodziny funkcji. W efekcie:
int scanner(const char *data, char *buffer, size_t buflen)
{
char format[32];
if (buflen == 0)
return 0;
snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
return sscanf(data, format, buffer);
}
Uwaga, to nadal ogranicza dane wejściowe do rozmiaru podanego jako „bufor”. Jeśli potrzebujesz więcej miejsca, musisz dokonać alokacji pamięci lub użyć niestandardowej funkcji bibliotecznej, która alokuje pamięć za Ciebie.
Należy zauważyć, że POSIX 2008 (2013) wersja scanf()
rodziny funkcji obsługuje modyfikator formatu m
(o charakterze przydział alokacji) dla wejść strunowych ( %s
, %c
, %[
). Zamiast pobierać char *
argument, pobiera char **
argument i przydziela niezbędną przestrzeń dla odczytywanej wartości:
char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
printf("String is: <<%s>>\n", buffer);
free(buffer);
}
Jeśli sscanf()
funkcja nie spełnia wszystkich specyfikacji konwersji, cała pamięć, którą zaalokowała na potrzeby %ms
konwersji podobnych do tych , jest zwalniana, zanim funkcja zwróci.
buflen-1
- dziękuję. Następnie musisz się martwić o niepodpisany niedomiar (zawijanie do dość dużej liczby), stądif
test. Byłbym bardzo kuszony, aby zastąpić to znakiemassert()
lub zarchiwizować goassert()
przed tym,if
który uruchomi się podczas programowania, jeśli ktoś jest na tyle nieostrożny, aby podać 0 jako rozmiar. Nie przejrzałem dokładnie dokumentacji, co%0s
oznaczasscanf()
- test może być lepszy, jakif (buflen < 2)
.snprintf
zapisuje niektóre dane do bufora ciągów isscanf
odczytuje z utworzonego ciągu. Gdzie dokładnie to zastępujescanf
w tym, że czyta ze standardowego wejścia?snprintf
ale nie jest to faktyczny parametr formatu.data
i dlategosscanf()
są odpowiednie. Jeśli zamiast tego chcesz czytać ze standardowego wejścia, usuńdata
parametr i wywołajscanf()
zamiast tego. Jeśli chodzi o wybór nazwyformat
zmiennej, która staje się ciągiem formatującym w wywołaniusscanf()
, możesz zmienić jej nazwę, jeśli chcesz, ale jej nazwa nie jest niedokładna. Nie jestem pewien, która alternatywa ma sens; byin_format
uczynić go być jaśniejsze? Nie planuję zmieniać tego w tym kodzie; możesz, jeśli wykorzystasz ten pomysł we własnym kodzie.scanf()
macOS nie jest udokumentowany jako pomocniczy%ms
, chociaż byłby użyteczny.Jeśli używasz gcc, możesz użyć specyfikatora rozszerzenia GNU, aby funkcja
a
scanf () przydzieliła pamięć do przechowywania danych wejściowych:int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; }
Edycja: Jak zauważył Jonathan, powinieneś zapoznać się ze
scanf
stronami podręcznika, ponieważ specyfikator może być inny (%m
) i może być konieczne włączenie pewnych definicji podczas kompilacji.źródło
m
modyfikator do tego samego zadania. Zobaczscanf()
. Musisz sprawdzić, czy używane systemy obsługują ten modyfikator.%ms
. Notacja%a
jest synonimem%f
(na wyjściu żąda szesnastkowych danych zmiennoprzecinkowych). Stronascanf()
podręcznika GNU mówi: _ Nie jest dostępna, jeśli program jest skompilowany zgcc -std=c99
lub gcc -D_ISOC99_SOURCE (chyba że_GNU_SOURCE
jest również określony), w którym to przypadkua
jest interpretowany jako specyfikator liczb zmiennoprzecinkowych (patrz wyżej) ._W większości przypadków połączenie
fgets
isscanf
spełnia swoje zadanie. Inną rzeczą byłoby napisanie własnego parsera, jeśli dane wejściowe są dobrze sformatowane. Zwróć też uwagę, że twój drugi przykład wymaga pewnych modyfikacji, aby mógł być bezpiecznie używany:#define LENGTH 42 #define str(x) # x #define xstr(x) str(x) /* ... */ int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array);
Powyższe powoduje odrzucenie strumienia wejściowego do góry, ale nie obejmuje
\n
znaku nowej linii ( ). Będziesz musiał dodać a,getchar()
aby to wykorzystać. Sprawdź również, czy osiągnąłeś koniec transmisji:if (!feof(stdin)) { ...
i to wszystko.
źródło
feof
kod w szerszym kontekście? Pytam, ponieważ ta funkcja jest często używana nieprawidłowo.array
musi byćchar array[LENGTH+1];
Bezpośrednie użycie
scanf(3)
i jego warianty stwarza szereg problemów. Zwykle użytkownicy i nieinteraktywne przypadki użycia są definiowane za pomocą wierszy danych wejściowych. Rzadko można spotkać przypadek, w którym, jeśli nie zostanie znaleziona wystarczająca liczba obiektów, więcej linii rozwiąże problem, ale jest to domyślny tryb scanf. (Jeśli użytkownik nie wiedział, jak wpisać liczbę w pierwszej linii, druga i trzecia linia prawdopodobnie nie pomogą.)Przynajmniej jeśli
fgets(3)
wiesz, ile wierszy wejściowych będzie potrzebnych Twój program i nie będziesz mieć żadnych przepełnień bufora ...źródło
Ograniczenie długości wejścia jest zdecydowanie łatwiejsze. Możesz zaakceptować dowolnie długie dane wejściowe, używając pętli, czytając po jednym kawałku, ponownie przydzielając miejsce dla ciągu w razie potrzeby ...
Ale to dużo pracy, więc większość programistów C po prostu odcina dane wejściowe na dowolnej długości. Przypuszczam, że już to wiesz, ale użycie fgets () nie pozwoli ci zaakceptować dowolnej ilości tekstu - nadal będziesz musiał ustawić limit.
źródło
realloc()
bufor.Stworzenie funkcji, która przydziela potrzebną pamięć dla twojego łańcucha, nie wymaga wiele pracy. To mała funkcja c, którą napisałem jakiś czas temu, zawsze używam jej do czytania w łańcuchach.
Zwróci odczytany ciąg lub jeśli wystąpi błąd pamięci NULL. Ale pamiętaj, że musisz zwolnić () swój ciąg i zawsze sprawdzać, czy jest zwracana.
#define BUFFER 32 char *readString() { char *str = malloc(sizeof(char) * BUFFER), *err; int pos; for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++) { if(pos % BUFFER == BUFFER - 1) { if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL) free(str); str = err; } } if(str != NULL) str[pos] = '\0'; return str; }
źródło
sizeof (char)
jest z definicji1
. Nie potrzebujesz tego tutaj.strerror(3)
) lub oczekiwać wstępnie przydzielone ciąg przekazany w (jak (strerror_r(3)
- lubscanf(3)
) ...