I „m într-o clasă care folosește C, iar instructorul meu are din păcate, a folosit gets()
în eșantionul de cod.
Deoarece aceasta este evident o supraveghere urâtă, care poate provoca un comportament nedefinit și alte probleme diferite (doar puțin sarcasm), am decis să pun în aplicare gets_s()
, deoarece a fost un exercițiu distractiv și, uneori, nu merită să facă verificarea completă a erorilor cu fgets()
și doriți doar să tăiați liniile lungi neașteptate.
Nu mă îngrijorează dacă aceasta implementează pe deplin gets_s()
așa cum este specificat în standardul C11 – se presupune că acesta este doar un înlocuitor pentru gets()
care nu vă depășește bufferul.
Cu toate acestea, ceea ce este foarte important este că această funcție face efectiv ceea ce face publicitate: este sigur și nu depășește memoria tampon.
Acesta este fiul meu prima dată când lucrez în C (de obicei folosesc java sau kotlin ) și apreciez toate sfaturi, deși aș dori cel puțin câteva mențiuni despre siguranța acestui cod și mă interesează și portabilitatea (către compilatoarele actuale).
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 un mic fișier de testare:
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; }
Se compilează cu succes cu gcc -Wall -Wextra -Wpedantic -Werror gets_s.c
, astfel încât „sa plus.
Răspuns
Mai întâi, nu-l numi gets_s
deoarece semnătura și comportamentul diferă în unele moduri subtile și nu atât de subtile, care doar duce la confuzie și frustrare. Oricum, într-adevăr nu veți dori contractul gets_s
.
Numiți-l ceva descriptiv precum getline_truncated
.
Știți că n <= 0
este UB în implementarea dvs.?
if (temp == EOF || temp = "\n") ^
Sunt sigur că compilatorul dvs. vă avertizează despre greșeala de mai sus.
Sau nu-i cereți toate avertismentele (-Wall -Wextra -std=...
)?
Vă sugerez redefinirea și redenumirea codurilor de returnare pentru a permite o mai bună testare a rezultatelor:
#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
În acest fel puteți testa ==0
sau >=0
în funcție de ceea ce decideți că este „succes”, respectiv !=0
sau <0
pentru „eșec”.
Comentarii
Răspuns
-
Codul poate citi prea mult:
temp = fgetc( stdin ); if (temp == EOF || temp = "\n") return GETS_S_OK;
Dacă
temp
nu este niciEOF
și nici\n
, personajul este pierdut. Ar fi mai bineungetc()
. -
Codul depășește mandatul. Bucla
do temp = fgetc( stdin ); while ( temp != EOF && temp != "\n" );
garantează că apelantul nu va vedea niciodată un șir gol. Uneori șirurile goale au semnificație semantic.
-
man fgets
:Funcțiile fgets () și gets () nu fac distincție între sfârșitul fișierului și eroare, iar apelanții trebuie să utilizeze feof ( 3) și feror (3) pentru a determina care a avut loc.
Sunteți într-o poziție excelentă pentru a face exact acest lucru. În loc să reveniți orbește
GETS_S_ERROR
, determinați ce s-a întâmplat și reveniți în consecință. De exemplu,#define GETS_S_EOF 3
.
Comentarii
- ' imit
gets()
prin faptul că ' citesc până la sfârșitul rândului, ideea fiind că ' este ceea ce cineva care ar fi folosit vrea. Dacă doriți comportamentul stop-at-n-minus-one-characters, folosiți doarfgets()
. Sau cel puțin, acesta a fost motivul meu pentru a alege acel comportament. (Dacă doriți să spuneți că ' citesc prea mult, spuneți de ce ați alege în mod explicit acel comportament. Am ales să citesc în continuare pentru a imitagets()
dacă nu ' nu a trecut de buffer-ul dvs.) - @ CAD97 Înțeleg că obiecția dvs. se referă la primul meu punct glonț. Codul dvs. imită într-adevăr
gets
și într-adevăr nu ' nu depășește memoria tampon.Ideea mea este căgets
este rupt în mai multe moduri.getline
mai mult sau mai puțin le rezolvă pe toate.
temp = "\n"
este o greșeală de greșeală numai postare; inițial am scris codul pe o mașină separată, apoi l-am retipat pentru întrebare. Sunt ' sigur că ar fi făcut parte din codul meu uld au greșit (așa cum am compilat cu-Wall -Wextra -Wpedantic -Werror
.'\n' == temp
– în acest fel, dacă greșesc==
la=
, veți obține o eroare mult mai clară