Jestem w klasie, która używa języka C, a mój instruktor ma niestety użył gets()
w przykładowym kodzie.
Ponieważ jest to oczywiście ohydne przeoczenie, które może powodować nieokreślone zachowanie i inne różne problemy (tylko trochę sarkazm), zdecydowałem się wdrożyć gets_s()
, ponieważ było to fajne ćwiczenie i czasami po prostu nie warto aby wykonać pełne sprawdzenie błędów za pomocą fgets()
i po prostu chcesz skrócić nieoczekiwanie długie wiersze.
Nie martwię się, czy to w pełni implementuje gets_s()
zgodnie ze standardem C11 – ma to być zwykły zamiennik gets()
, który nie” przepełnia bufora.
Jednak bardzo ważne jest to, że ta funkcja faktycznie robi to, co ogłasza: „jest bezpieczna” i nie przepełnia bufora.
To jest mój plik fi pierwszy raz pracuję w C (zwykle używam java lub kotlin ) i doceniam wszystko wskazówki, chociaż chciałbym chociaż trochę wspomnieć o bezpieczeństwie tego kodu, a także interesuje mnie przenośność (do obecnych kompilatorów).
gets_s.h
#include <stdio.h> #include <string.h> #define GETS_S_OK 0 #define GETS_S_ERROR 1 #define GETS_S_OVERRUN 2 static inline int gets_s( char str[], int n ) { char *str_end, *fgets_return; int temp; fgets_return = fgets( str, n, stdin ); /* If fgets fails, it returns NULL. This includes the case where stdin is exhausted. */ if ( fgets_return == NULL ) { str[0] = "\0"; return GETS_S_ERROR; } str_end = str + strlen(str) - 1; if ( str_end == "\n" ) { *str_end = "\0"; return GETS_S_OK; } temp = fgetc( stdin ); if (temp == EOF || temp = "\n") return GETS_S_OK; do temp = fgetc( stdin ); while ( temp != EOF && temp != "\n" ); return GETS_S_OVERRUN; }
i mały plik testowy:
gets_s.c
#include "gets_s.h" #include <stdio.h> int main() { char buffer[10]; int gets_s_return; printf("Enter up to %d characters safely.\n", sizeof(buffer) - 1); gets_s_return = gets_s( buffer, sizeof(buffer) ); printf("buffer = %s", buffer); printf("gets_s return = %d", gets_s_return); return 0; }
Kompiluje się pomyślnie z gcc -Wall -Wextra -Wpedantic -Werror gets_s.c
, więc to „plus.
Odpowiedź
Po pierwsze, nie nazywaj tego gets_s
, ponieważ podpis i zachowanie różnią się w subtelny i niezbyt subtelny sposób. prowadzi do zamieszania i frustracji. W każdym razie naprawdę nie chciałbyś gets_s
-kontraktu.
Nazwij to czymś opisowym, np. getline_truncated
.
Czy wiesz, że n <= 0
jest UB w Twojej implementacji?
if (temp == EOF || temp = "\n") ^
Jestem pewien, że kompilator ostrzega Cię o powyższej literówce.
Albo nie pytaj go o wszystkie ostrzeżenia (-Wall -Wextra -std=...
)?
Proponuję ponowne zdefiniowanie i zmiana nazw kodów powrotu, aby umożliwić lepsze testowanie wyników:
#define GETS_S_TRUNCATED 1 // Because truncation is not neccessarily an error #define GETS_S_OK 0 #define GETS_S_ERROR EOF // Because we already have an appropriate negative constant
W ten sposób możesz przetestować ==0
lub >=0
w zależności od tego, co uznasz za „sukces”, odpowiednio !=0
lub <0
dla „awarii”.
Komentarze
Odpowiedź
-
Kod może czytać za dużo:
temp = fgetc( stdin ); if (temp == EOF || temp = "\n") return GETS_S_OK;
Jeśli
temp
nie jest aniEOF
, ani\n
postać zostaje utracona. Lepiejungetc()
to. -
Kod przekracza mandat. Pętla
do temp = fgetc( stdin ); while ( temp != EOF && temp != "\n" );
gwarantuje, że wywołujący nigdy nie zobaczy pustego ciągu. Czasami puste łańcuchy mają znaczenie semantyczne.
-
man fgets
:Funkcje fgets () i gets () nie rozróżniają między końcem pliku a błędem, a wywołujący muszą używać feof ( 3) i ferror (3), aby określić, co się wydarzyło.
Jesteś w doskonałej pozycji, aby to zrobić. Zamiast na ślepo zwracać
GETS_S_ERROR
, określ, co się stało i wróć odpowiednio. Np.#define GETS_S_EOF 3
.
Komentarze
- I ' naśladuję
gets()
w tym, że ' m czyta do końca wiersza, przy czym chodzi o to, że ' jest tym, co ktoś, kto użyłby chce. Jeśli chcesz zachować zachowanie stop-at-n-minus-one-character, po prostu użyjfgets()
. A przynajmniej to był mój powód, dla którego wybrałem takie zachowanie. (Jeśli chcesz powiedzieć, że ' czytam za dużo, powiedz, dlaczego wybrałbyś to zachowanie wprost. Postanowiłem kontynuować czytanie, aby naśladowaćgets()
jeśli nie ' nie zapisuje poza twoim buforem.) - @ CAD97 Rozumiem, że Twój sprzeciw dotyczy mojego pierwszego punktu. Twój kod rzeczywiście naśladuje
gets
i rzeczywiście nie ' nie przepełnia bufora.Chodzi mi o to, żegets
jest uszkodzony na więcej niż jeden sposób.getline
mniej więcej rozwiązuje je wszystkie.
temp = "\n"
to literówka w tym tylko post; pierwotnie napisałem kod na oddzielnym komputerze, a następnie przepisałem go w odpowiedzi na pytanie. ' m na pewno, jeśli byłby to część mojego kodu, to wo uld popełniło błąd (jak skompilowałem z-Wall -Wextra -Wpedantic -Werror
.'\n' == temp
– w ten sposób, jeśli wpiszą literę==
do=
, otrzymasz znacznie bardziej określony błąd