Czytanie łańcucha za pomocą scanf

147

Jestem trochę zdezorientowany. Miałem wrażenie, że poprawny sposób odczytu napisu C scanf()przebiega zgodnie z liniami

(nieważne możliwe przepełnienie bufora, to tylko prosty przykład)

char string[256];
scanf( "%s" , string );

Jednak wydaje się, że działa też:

scanf( "%s" , &string );

Czy to tylko mój kompilator (gcc), szczęście czy coś innego?

abeln
źródło
W drugim przypadku w rzeczywistości nie ma możliwości przepełnienia bufora, ponieważ w ogóle go nie używasz. Albo to, albo możesz powiedzieć, że jakikolwiek ciąg dłuższy niż 3 znaki przepełni twój "bufor".
TED
Miałem na myśli pierwszy przykład. Ponadto inni już wskazali, co się tutaj dzieje.
abeln
Tak. Wypróbowałem to i Gareth ma rację. Dziwne.
TED
Ponieważ to pytanie jest zwracane przez wyszukiwanie „jak odczytać ciąg za pomocą scanf”, może być stosowne wskazanie, że to pytanie dotyczy sposobów przekazania wskaźnika do bufora scanf, a zarówno pytanie, jak i zaakceptowana odpowiedź skupiają się na to i pomiń krytycznie ważne ograniczenia dotyczące maksymalnej długości danych wejściowych, które powinny być używane w rzeczywistym kodzie (ale poza tym, o co chodzi w tym pytaniu).
Arkku

Odpowiedzi:

141

Tablica „rozpada się” na wskaźnik do swojego pierwszego elementu, więc scanf("%s", string)jest równoważna scanf("%s", &string[0]). Z drugiej strony scanf("%s", &string)przekazuje wskaźnik-do- char[256], ale wskazuje to samo miejsce.

Następnie scanf, podczas przetwarzania końca listy argumentów, spróbuje wyciągnąć plik char *. To jest właściwa rzecz, kiedy zdałeś stringlub &string[0], ale kiedy zdałeś &string, jesteś zależny od czegoś, czego standard językowy nie gwarantuje, a mianowicie, że wskaźniki &stringi &string[0]- wskazują na obiekty różnych typów i rozmiarów, które rozpocząć w tym samym miejscu - są reprezentowane w ten sam sposób.

Nie wierzę, że kiedykolwiek spotkałem system, w którym to nie działa, aw praktyce prawdopodobnie jesteś bezpieczny. Niemniej jednak jest to błąd i może zawieść na niektórych platformach. (Hipotetyczny przykład: implementacja "debugowania", która zawiera informacje o typie przy każdym wskaźniku. Myślę, że implementacja C w Symbolics "Lisp Machines" zrobiła coś takiego.)

Gareth McCaughan
źródło
5
+1. Jest również trywialne sprawdzenie, czy zanik tablicy skutkuje &stringtym samym działaniem string(zamiast powodować przypadkowe uszkodzenie pamięci, jak twierdzą inne odpowiedzi):printf("%x\n%x\n", string, &string);
Josh Kelley
@Josh Kelley ciekawe, wziąłbym wtedy, że tak nie byłoby w przypadku wskaźnika do ciągu przypisanego przez malloc ()?
abeln
4
@abeln: Dobrze. Dla łańcucha przydzielonego przez malloc () stringjest wskaźnikiem i &stringjest adresem tego wskaźnika, więc te dwa elementy NIE są zamienne. Jak wyjaśnia Gareth, nawet w przypadku zaniku tablicy stringi &stringtechnicznie nie są tego samego typu (nawet jeśli zdarza się, że są wymienne dla większości architektur), a gcc wyświetli ostrzeżenie dla drugiego przykładu, jeśli włączysz -Wall.
Josh Kelley
@Josh Kelley: dzięki, cieszę się, że mogłem o tym pomyśleć.
abeln
1
@JCooper str, & str [0] oba reprezentują to samo (początek tablicy) i chociaż niekoniecznie & str jest również tym samym adresem pamięci. Teraz strPtr wskazuje na str, więc adres pamięci przechowywany wewnątrz strPtr jest taki sam jak str i to jest 12fe60. Wreszcie & strPtr jest adresem zmiennej strPtr, nie jest to wartość przechowywana w strptr, ale rzeczywisty adres pamięci strPtr. Ponieważ strptr jest inną zmienną niż wszystkie inne, ma również inny adres pamięci, w tym przypadku 12fe54
Juan Besa
-9

Myślę, że to poniżej jest dokładne i może pomóc. Jeśli znajdziesz jakieś błędy, możesz je poprawić. Jestem nowy w C.

char str[]  
  1. tablica wartości typu char, z własnym adresem w pamięci
  2. tablica wartości typu char, z własnym adresem w pamięci tyle samo kolejnych adresów, ile elementów w tablicy
  3. w tym znak zerowy zakończenia '\0' &str, &str[0]a strwszystkie trzy reprezentują to samo miejsce w pamięci, które jest adresem pierwszego elementu tablicystr

    char * strPtr = & str [0]; // deklaracja i inicjalizacja

alternatywnie możesz podzielić to na dwie części:

char *strPtr; strPtr = &str[0];
  1. strPtr jest wskaźnikiem do char
  2. strPtr punkty w szyku str
  3. strPtr jest zmienną z własnym adresem w pamięci
  4. strPtr to zmienna przechowująca wartość adresu &str[0]
  5. strPtr własny adres w pamięci różni się od adresu pamięci, który przechowuje (adres tablicy w pamięci, czyli & str [0])
  6. &strPtr reprezentuje adres samego strPtr

Myślę, że możesz zadeklarować wskaźnik do wskaźnika jako:

char **vPtr = &strPtr;  

deklaruje i inicjalizuje z adresem wskaźnika strPtr

Alternatywnie możesz podzielić na dwie części:

char **vPtr;
*vPtr = &strPtr
  1. *vPtr wskazuje na wskaźnik strPtr
  2. *vPtr jest zmienną z własnym adresem w pamięci
  3. *vPtr to zmienna przechowująca wartość adresu & strPtr
  4. komentarz końcowy: nie możesz str++, stradres to a const, ale możesz to zrobićstrPtr++
angelita
źródło