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?


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.)


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++
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.