Jeg prøver å skrive en idiomatisk trimfunksjon i C. Hvordan ser dette ut? Skal jeg i stedet malloc den nye strengen og returnere den?
void trim(const char *input, char *result) { int i, j = 0; for (i = 0; input[i] != "\0"; i++) { if (!isspace(input[i])) { result[j++] = input[i]; } } }
Kommentarer
- Der ‘ et antall problemer med den koden, det ‘ er sårbart for bufferoverløpsangrep, og det ‘ t gjør hva en typisk » trim » -funksjon. Trim fjerner ledende og etterfølgende mellomrom. Dette fjerner dem alle.
- Takk. Kan du snakke om hvordan du håndterer bufferoverløpsangrep?
- Du bør aldri blindt kopiere data til noen buffer når du ikke ‘ ikke vet hvor mye plass som er tildelt til det, at ‘ bare ber om problemer. En enkel ting å gjøre vil være å legge til en parameter som tar inn størrelsen på bufferen. På den måten er det ‘ som ringer opp for å fortelle deg hvor stor den egentlig er. Så er det ‘ opp til deg å aldri prøve å lese / skrive utover gitt lengde. Selvfølgelig er det ‘ ikke idiotsikkert, den som ringer kan gi deg falske lengder, men det ville være et problem i deres ende, ikke din.
Svar
Som @JeffMercado påpekte, fjerner dette mellomrom i stedet for å trimme ledende og etterfølgende mellomrom. Forutsatt at du vil beholde den nåværende funksjonaliteten, la oss kalle det remove_spaces
.
Det er en veldig subtil feil her:
... isspace(input[i]) ...
isspace
tar verdien av en usignert tegn eller EOF
. Hvis du sender det til char
, som vanligvis er signert, vil det føre til udefinert oppførsel. Si i stedet:
... isspace((unsigned char) input[i]) ...
En annen feil: du sender ikke ut en NUL-terminator, noe som betyr at den som ringer ikke har noen måte å vite hvor lang strengen er ( med mindre den nulstilte bufferen før du ringte til funksjonen din.
Å fikse disse feilene gir oss:
void remove_spaces(const char *input, char *result) { int i, j = 0; for (i = 0; input[i] != "\0"; i++) { if (!isspace((unsigned char) input[i])) { result[j++] = input[i]; } } result[j] = "\0"; }
@JeffMercado sa også denne funksjonen er sårbar for bufferoverløp. På en måte er dette ikke sant, forutsatt at den som ringer vet å tildele en buffer på minst strlen(input) + 1
. Men innringeren kan være lat og bare si char result[100]
. Å legge til en parameter for utgangsbufferstørrelse vil sannsynligvis beskytte mot en slik feil:
void remove_spaces(const char *input, char *output, size_t output_size);
Se om du kan implementere dette Noen ting å huske på:
-
Ikke glem NUL-terminatoren når du sjekker størrelsen på utgangsbufferen.
-
Ikke vær som strncpy og utelat NUL-terminatoren når du må avkutte strengen, da det kan føre til subtile feil.
-
Hvis du bruker
int
fori
ogj
ogsize_t
foroutput_size
, bør du få kompilatoradvarsler om sammenligning mellom signert og usignert. Hvis du ikke gjør det, skru opp kompileringsadvarslene dine. Hvis du bruker GCC fra kommandolinjen, må du vane å skrivegcc -Wall -W
.
Kommentarer
-
strncpy()
er ikke en strengfunksjon, selv selv om noen antar det. Så resultatet å være en streng vil uansett være tilfelle. Som i beste fall gjør analogien sketchy.
Svar
Vi vet at vi kan bevege en peker fremover og bakover , og vi vet også at vi kan trimme en streng fra venstre. Hvis vi øker pekeren og reduserer pekeren for å trimme fra høyre, er det nok med
sløyfer. Du vil merke at antall gange for høyre er mindre enn antall gange for venstre.
Høyre trim-kode:
#include <stdio.h> #include <ctype.h> void trim_both(char *, char *); int main (void) { char title[100] = " My long string "; char title_t[100] = ""; (void) printf("String before left trim is:[%s]\n", title); trim_both(title, title_t); (void) printf("String after left trim is:[%s]\n", title_t); } // trim spaces from left void trim_both(char *title_p, char *title_tp) { int flag = 0; // from left while(*title_p) { if(!isspace((unsigned char) *title_p) && flag == 0) { *title_tp++ = *title_p; flag = 1; } title_p++; if(flag == 1) { *title_tp++ = *title_p; } } // from right while(1) { title_tp--; if(!isspace((unsigned char) *title_tp) && flag == 0) { break; } flag = 0; *title_tp = "\0"; } }
Svar
Enkleste måte (fjerner bare mellomrom):
Trim. Start:
- Sammenlign tegn til de tilsvarer
" "
(mellomrom eller andre tegn som\n
eller\t
) ved strengstart og øk temp (i
) variabel. - Flytt pekeren rundt
i
(str+=i
). Nå starter strengen fra røye som ikke er mellomrom (eller annen hvit røye).
Trim.End:
- Gjør det samme for Trim.Start men fra slutten av strengen.
- Sett siste tegn (siste mellomrom) som
\0
.
Det viktige er at funksjonen tar pekeren til pekeren (streng).Se etter funksjonsanropet: StringTrim(&p2);
char * StringTrim(char * *pointerToString) { u8 start=0, length=0; // Trim.Start: length = strlen(*pointerToString); while ((*pointerToString)[start]==" ") start++; (*pointerToString) += start; if (start < length) // Required for empty (ex. " ") input { // Trim.End: u8 end = strlen(*pointerToString)-1; // Get string length again (after Trim.Start) while ((*pointerToString)[end]==" ") end--; (*pointerToString)[end+1] = 0; } return *pointerToString; }
Bruk:
char str1[] = " test1 "; char * p1 = str1; Debug("1. before trim: [%s]", p1); StringTrim(&p1); Debug("1. after trim [%s]", p1); char str2[] = " test2"; char * p2 = str2; Debug("2. before trim: [%s]", p2); StringTrim(&p2); Debug("2. after trim [%s]", p2); char str3[] = "test3 "; char * p3 = str3; Debug("3. before trim: [%s]", p3); StringTrim(&p3); Debug("3. after trim [%s]", p3); char str4[] = " "; char * p4 = str4; Debug("4. before trim: [%s]", p4); StringTrim(&p4); Debug("4. after trim [%s]", p4); char str5[] = ""; char * p5 = str5; Debug("5. before trim: [%s]", p5); StringTrim(&p5); Debug("5. after trim [%s]", p5);
Resultat :
1. before trim: [ test1 ] 1. after trim [test1] 2. before trim: [ test2] 2. after trim [test2] 3. before trim: [test3 ] 3. after trim [test3] 4. before trim: [ ] 4. after trim [] 5. before trim: [] 5. after trim []