2016-12-20 49 views
6

Czy użytkownik może wpisać terminator typu null na wejściu, dla którego używany jest scanf, więc długość wejścia będzie wynosić 0?

Użytkownik wpisujący terminator null w scanf

char msg[1000]; 
scanf("%1000s",msg); // the user would type nothing: '' or '\0' 
+0

Dla formatu ['scanf'] (http://en.cppreference.com/w/c/io/fscanf)' "% s" 'musi istnieć * trochę * wprowadzania spoza przestrzeni. Jeśli chcesz opcjonalne wejście, dlaczego nie użyć zamiast tego ['fgets'] (http://en.cppreference.com/w/c/io/fgets)? –

+0

@Someprogrammerdude Nie mogę używać fgets, musi to być scanf –

+1

Następnie musisz żyć z ograniczeniami, że nie możesz podać niepustych danych wejściowych. –

Odpowiedz

8

W wielu systemach, odpowiedź brzmi "Tak".

Zazwyczaj magiczna sekwencja to Control- @.

Kod znaku @ ma zwykle wartość 64, a jeden mniej niż A (65). Control-A to kod znaku 1; jeden mniej to kod znaku 0, alias '\0'.

Pamiętaj, że aby uzyskać wejście o zerowej długości, musisz wpisać bajt zerowy jako pierwszy znak spoza przestrzeni, a aby wejść do wejścia, musisz nacisnąć przycisk powrotu. W Twoim programie trudno byłoby określić, co jeszcze zostało wprowadzone po bajcie zerowej.

0

Tak, jest to możliwe, ponieważ '\0' to nie-biała przestrzeń. scanf() uzna, że ​​jest to koniec ciągu. Tak więc %s może dopasować pusty ciąg.

Można użyć specyfikatora m, aby przydzielić odpowiedni bufor do przechowywania danych wejściowych. Zauważ, że jest to standard POSIX 200112L.

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 

int main(void) { 
    char *str; 
    int n; 
    if (scanf("%ms%n", &str, &n) == 1) { 
     printf("I read %d character(s).\n", n); 
     if (strlen(str) != 0) { 
     printf("str = %s\n", str); 
     } 
     else { 
     printf("str is empty"); 
     } 
     free(str); 
    } 
    else { 
     printf("The user enter nothing"); 
    } 
} 
+0

'free (str);'?!?! Gdzie jest odpowiednie "malloc()"? –

+4

@AndrewHenle In [scanf] (https://linux.die.net/man/3/scanf) patrz modyfikator m. – Stargateur

+2

Nice. Nauczyłem się dzisiaj czegoś nowego. Dzięki. (I nie, nie byłem downvoter.) –

4

Niektóre systemy pozwalają użytkownikowi terminala wpisać bajt NUL naciskając Ctrl - @. scanf() zapisałby następnie ten bajt do tablicy docelowej, ale najprawdopodobniej nie uznałby tego bajtu za separator słów, więc parsowanie wejściowe będzie kontynuowane aż do końca pliku lub wprowadzony zostanie znak odstępu, taki jak znak nowej linii.

Nie da się określić z tablicy docelowej, czy bajt NUL został zapisany po innych znakach, ani ile kolejnych znaków zostało przeczytanych po tym bajcie NUL. Ale jeśli scanf() zwróci 1 ima zerową długość, jedyną możliwością jest to, że użytkownik wprowadził bajt NUL po możliwie pustej sekwencji białych znaków.

Byłoby to również jedyna możliwość dla fgets() wrócić pusty ciąg kiedy minął tablicę docelową o rozmiarze większym niż 1.

Jeśli wejście jest czytane z pliku zamiast terminalu, to zawsze możliwość, aby plik zawierał bajty NUL i należy zachować ostrożność, aby uniknąć niezdefiniowanego zachowania, jeśli tak się stanie. Na przykład o to klasyczny błąd:

char msg[1000]; 
if (fgets(msg, sizeof msg, stdin)) { 
    int len = strlen(msg); 
    if (msg[len - 1] == '\n') { // potential undefined behavior! 
     msg[--len] = '\0'  // potential undefined behavior! 
    } 
    ... 
} 

Należy również pamiętać, że kod ma wadę: maksymalna liczba znaków do przechowywania do miejsca przeznaczenia powinna być 999 nie 1000:

char msg[1000]; 
if (scanf("%999s",msg) == 1) { 
    /* word was read from the user */ 
    if (*msg == '\0') { 
     printf("user entered a NUL byte\n"); 
    } 
} 
2

Czy użytkownik może wpisać terminator typu "null" na wejściu, dla którego używany jest scanf, więc długość wejścia będzie wynosić 0?

Ponadto użytkownik typing in a null character, stdin można użyć przekierowane wkład zawierający znaki zerowe.

foo < data.bin 

Szczegóły: Kiedy pierwszy znak skanowany jest znak null lub '\0', to czyta się jak każdy inny charakter i zapisywane jak każdy inny charakter non-white-space. Długość jest więc wciąż większa niż 0, ale wartość zgłoszona przez strlen(msg) jest oszukiwana przez osadzony znak zerowy i zwraca zero.

fgets() ma ten sam problem co scanf() w tym względzie: strlen(msg) nie sprecyzowane raportu liczba znaków czytać.

Niestandardowy getline() zwraca liczbę odczytanych znaków i jest wyjściem z tego.


@Stargateur wspomina używając "%n" przechowywać liczbę znaków zapisanych w msg. Być jak

char msg[1000 + 1]; 
int start, end; 
if (1 == scanf(" %n%1000s%n", &start, msg, &end)) { 
    printf("%d characters read\n", end - start); 
} 

IAC, staramy się radzić sobie ze słabością narożnej scanf() i rodziny. Najlepsze rozwiązanie odczytu wejściowe zależy od dodatkowych czynników, nie wymienionych przez OP.