2009-11-29 11 views
5

wiem w C można zadeklarować ciąg i liczbę znaków takich jak poniżej,Ustalenie Długość Char String w C - jeśli użytkownik wprowadza treść napisu na

char mystring[50]; 

z „50” oznacza liczbę znaków .

Jaka jest właściwa procedura, jeśli użytkownik ma zamiar wprowadzić zawartość ciągu (przez scanf ("% s", mystring);)? Czy pozostawię to jako, pozostawiając go jako "0", ponieważ nie mam pojęcia, ile znaków użytkownik wprowadzi?

Albo mam zrobić,

char mystring[400]; 

dając do 400 znaków dla użytkownika do wejścia?

Odpowiedz

6

Trafiłeś na dokładny problem z scanf() i% s - co się dzieje, gdy nie wiesz, ile masz danych wejściowych?

Jeśli spróbujesz uruchomić char mystring[0];, Twój program skompiluje się dobrze. Ale zawsze będziesz segfault. Tworzysz tablicę o rozmiarze 0, więc gdy spróbujesz umieścić w tej tablicy coś, co stanie się, natychmiast wyjdziesz poza granice łańcucha znaków (ponieważ nie zostanie przydzielona żadna pamięć) - czyli jest to błąd segfault.

Zatem punkt 1: należy zawsze przydzielać rozmiar do napisu. Mogę myśleć o bardzo niewielu okolicznościach (w porządku, nie ma), gdzie chciałbyś powiedzieć char mystring[0] zamiast char *mystring.

Następnie, gdy używasz scanf, nigdy nie będziesz chciał używać specyfikatora "% s" - ponieważ nie będzie to miało wpływu na wielkość łańcucha znaków. więc nawet jeśli masz:

char mystring[512]; 
scanf("%s", mystring); 

jeśli użytkownik wprowadzi więcej niż 511 znaków (od 512th jest \ 0), można wyjść z granic swojej tablicy.Sposobem na zaradzenie to:

scanf("%511s", mystring); 

To wszystko znaczy, że C nie posiada placówki, aby automatycznie dopasowuje ciąg, jeśli istnieje więcej niż wejście czekasz. To jest coś, co musisz zrobić ręcznie.

Jednym ze sposobów rozwiązania tego problemu jest użycie numeru fgets().

można powiedzieć:

while (fgets(mystring, 512, stdin)) 
{ 
    /* process input */ 
} 

Następnie można użyć sscanf(), aby analizować mystring

Spróbuj powyższy kod, z ciągiem długości 5. Po 4 znaków, które zostały przeczytane, że kod pętle ponownie, aby pobrać resztę danych wejściowych. "Przetwarzanie" może zawierać kod, aby ponownie przydzielić ciąg o większym rozmiarze, a następnie dołączyć najnowsze dane wejściowe z fgets().

Powyższy kod nie jest doskonały - spowodowałby, że twój program zapętliłby się i przetworzył dowolną nieskończoną długość łańcucha, więc możesz chcieć mieć wewnętrzny twardy limit (np. Pętla maksymalnie 10 razy).

+0

t należy dodać, że% s czyta słowa, a nie całe ciągi. Ponieważ ciąg formatu scanf używa spacji i znaków nowej linii jako ograniczników.W tym przypadku użyj% c zamiast tego (z szerokością pola) lub fgets, jak wspomniałeś. W przypadku% c z szerokością pola pamiętaj, aby zainicjować cały łańcuch bufora na zero. –

+0

Program nie zawsze ulega uszkodzeniu. W rzeczywistości, prawdopodobnie nie przez większość czasu. Twój program prawdopodobnie zostanie po cichu zepsuty. Czy C nie jest piękny? :-) –

2

Użytkownik zawsze będzie mógł wprowadzić więcej znaków, tym samym przepełniając bufor (wspólne źródło luk w zabezpieczeniach). Można jednak określić „szerokość pola” do scanf, tak:

scanf("%50s", mystring); 

W tym przypadku, gdy bufor powinien być 51 znaków, aby uwzględnić charakter pola 50 plus null terminatora. Lub uczynić 50 znaków z bufora i powiedzieć scanf 49 jest szerokość.

+0

ale przy zadeklarowaniu ciągu należy podać "0" lub dużą liczbę? – HollerTrain

+1

Powinieneś określić co najmniej 51, w tym przykładzie. (Długość + 1 dla terminatora zerowego.) – Thanatos

+0

ok. tak jest wymienienie go jako "0", gdy deklarujesz, że nie jest właściwe kodowanie? Mój problem polega na tym, że nie mam pojęcia, ilu użytkowników wprowadzi, ale w tym samym czasie chce się nauczyć prawidłowej metody ... – HollerTrain

2

Istnieje funkcja o nazwie ggets(), która nie jest częścią standardowej biblioteki C. To dość prosta funkcja. Inicjuje tablicę znaków za pomocą funkcji malloc(). Następnie odczytuje znaki z stdin po jednym znaku na raz. Śledzi liczbę odczytanych znaków i rozszerza tablicę znaków za pomocą realloc(), gdy zabraknie jej miejsca.

Jest on dostępny tutaj: http://cbfalconer.home.att.net/download/index.htm

Proponuję odczytać kodu i ponownie realizować siebie.

0

Normalną praktyką w C jest użycie czegoś podobnego GNU readline czy może NetBSD editline, aka libedit. (to samo API, innym wdrożenia i licencji na oprogramowanie).

Dla prostszej lub programu pracy domowej, można teoretycznie dać szerokość pola do scanf , ale bardziej normalną praktyką jest fgets() na macierz o stałej szerokości, a następnie uruchomienie na tym sscanf(). W ten sposób kontrolujesz liczbę odczytanych linii.

0

Jako przykład, jeśli użytkownik wprowadza swoje imię, nie zawsze jest bezpieczne, maksymalizując rozmiar "tajemniczego" jako 35 znaków, ponieważ niektóre osoby mają naprawdę długie nazwy. Nie chcesz dotrzeć do przypadku, w którym użytkownik nie może wprowadzić żądanych informacji w całości. Właściwym sposobem na to byłoby posiadanie tymczasowego bufora o bardzo dużym rozmiarze, który obejmie wszystkie możliwe dane wejściowe użytkownika. Gdy użytkownik wprowadzi informacje i zostanie zapisany w buforze, przeniesiesz znaki z bufora do mystringa, jednocześnie odrywając całą dodatkową przestrzeń na końcu bufora. Będziesz w stanie dokładnie określić rozmiar, jakiego potrzebujesz do "mystringu", i możesz zdiagnozować tylko taką ilość miejsca i odrzucić bufor. W ten sposób nie będziesz używał ciągu z większą ilością pamięci dla reszty programu ... użyjesz tylko napisu o wymaganej ilości pamięci.

+0

Nadal musiałbyś przeprowadzić jakąś kontrolę, aby upewnić się, że to, co użytkownik wprowadza, nie jest większe niż bufor przydzielony w bardzo rzadkich przypadkach lub gdy ktoś próbuje wykorzystać twój program. –

1

Jest to kod cbfalconer za (http://cbfalconer.home.att.net/download/index.htm) z kilkoma niewielkimi modyfikacjami i zebrane w jednym pliku:

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

#define INITSIZE 112 /* power of 2 minus 16, helps malloc */ 
#define DELTASIZE (INITSIZE + 16) 

enum {OK = 0, NOMEM}; 

int fggets(char* *ln, FILE *f) 
{ 
    int  cursize, ch, ix; 
    char *buffer, *temp; 

    *ln = NULL; /* default */ 
    if (NULL == (buffer = malloc(INITSIZE))) return NOMEM; 
    cursize = INITSIZE; 

    ix = 0; 
    while ((EOF != (ch = getc(f))) && ('\n' != ch)) { 
     if (ix >= (cursize - 1)) { /* extend buffer */ 
     cursize += DELTASIZE; 
     if (NULL == (temp = realloc(buffer, (size_t)cursize))) { 
      /* ran out of memory, return partial line */ 
      buffer[ix] = '\0'; 
      *ln = buffer; 
      return NOMEM; 
     } 
     buffer = temp; 
     } 
     buffer[ix++] = ch; 
    } 
    if ((EOF == ch) && (0 == ix)) { 
     free(buffer); 
     return EOF; 
    } 

    buffer[ix] = '\0'; 
    if (NULL == (temp = realloc(buffer, (size_t)ix + 1))) { 
     *ln = buffer; /* without reducing it */ 
    } 
    else *ln = temp; 
    return OK; 
} /* fggets */ 
/* End of ggets.c */ 

int main(int argc, char **argv) 
{ 
    FILE *infile; 
    char *line; 
    int cnt; 

    //if (argc == 2) 
     //if ((infile = fopen(argv[1], "r"))) { 
     cnt = 0; 
     while (0 == fggets(&line, stdin)) { 
      fprintf(stderr, "%4d %4d\n", ++cnt, (int)strlen(line)); 
      (void)puts(line); 
      free(line); 
     } 
     return 0; 
     //} 
    //(void)puts("Usage: tggets filetodisplay"); 
    //return EXIT_FAILURE; 
} /* main */ 
/* END file tggets.c */ 

Przetestowałem go i będzie zawsze daje to, co chcesz.

+0

Zasadniczo, aby uzyskać jego oryginalny kod, odkomentuj komentarze i zamień stdin na infile w wywołaniu fggets. –