Jeg prøver at skrive en idiomatisk trimfunktion i C. Hvordan ser det ud? Skal jeg i stedet mallocere den nye streng 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 antal problemer med den kode, det ‘ er sårbart over for bufferoverløbsangreb, og det ‘ t gør, hvad en typisk ” trim ” -funktion. Trim fjerner ledende og efterfølgende mellemrum. Dette fjerner dem alle.
- Tak. Kan du venligst uddybe, hvordan du håndterer bufferoverløbsangreb?
- Du bør aldrig blindt kopiere data til en eller anden buffer, når du ikke ‘ ikke ved, hvor meget plads der er tildelt til det, at ‘ bare beder om problemer. En simpel ting at gøre ville være at tilføje en parameter, der tager størrelsen på bufferen. På den måde ‘ er alt sammen på den, der ringer op, for at fortælle dig, hvor stor den virkelig er. Så er det ‘ op til dig at aldrig forsøge at læse / skrive ud over den givne længde. Selvfølgelig er det ‘ ikke dårligt bevis, den, der ringer, kan give dig falske længder, men det ville være et problem i deres ende, ikke din.
Svar
Som @JeffMercado påpegede, fjerner dette mellemrum i stedet for at trimme ledende og bageste mellemrum. Forudsat at du vil beholde den aktuelle funktionalitet, lad os kalde det remove_spaces
.
Der er en virkelig subtil fejl her:
... isspace(input[i]) ...
isspace
tager værdien af en usigneret tegn eller EOF
. At give det en char
, som normalt er underskrevet, vil frembringe udefineret adfærd. Sig i stedet:
... isspace((unsigned char) input[i]) ...
En anden fejl: Du udsender ikke en NUL-terminator, hvilket betyder, at den, der ringer op, ikke har nogen måde at vide, hvor lang strengen er ( medmindre det nulstillede bufferen, før du kalder din funktion).
At rette disse fejl giver os:
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 sagde også denne funktion er sårbar over for bufferoverløb. På en måde er dette ikke sandt, forudsat at den, der ringer, ved at tildele en buffer på mindst strlen(input) + 1
. Men den, der ringer op, kan være doven og bare sige char result[100]
. Tilføjelse af en parameter for outputbufferstørrelse vil sandsynligvis beskytte mod en sådan fejl:
void remove_spaces(const char *input, char *output, size_t output_size);
Se om du kan implementere dette Nogle ting at huske på:
-
Glem ikke NUL-terminatoren, når du kontrollerer outputbufferstørrelsen.
-
Må ikke være som strncpy og udelad NUL-terminatoren, når du skal afkorte strengen, da det kan føre til subtile bugs.
-
Hvis du bruger
int
tili
ogj
ogsize_t
foroutput_size
, skal du få advarsler om kompiler om sammenligning mellem underskrevet og usigneret. Hvis du ikke gør det, skal du skrue op for din compileradvarsel. Hvis du bruger GCC fra kommandolinjen, skal du vane at skrivegcc -Wall -W
.
Kommentarer
-
strncpy()
er ikke en strengfunktion, selv selvom nogle antager det. Så resultatet af at være en streng ville alligevel være tilfældet. Hvilket gør analogien i bedste fald sketchy.
Svar
Vi ved, at vi kan flytte en markør frem og tilbage , og vi ved også, at vi kan trimme en streng fra venstre. Hvis vi forøger markøren og mindsker markøren for at trimme fra højre, er to while
sløjfer nok. Du vil bemærke, at antallet af højre gåture er mindre end antallet af venstre gåture.
Højre 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
Den nemmeste måde (fjerner kun mellemrum):
Trim. Start:
- Sammenlign tegn indtil de er lig med
" "
(mellemrum eller andre tegn som\n
eller\t
) ved strengens start og stig temp. (i
) variabel. - Flyt markøren til
i
(str+=i
). Nu starter strengen fra char, som ikke er mellemrumstegn (eller nogen anden hvid char).
Trim.End:
- Gør det samme for Trim.Start men fra slutningen af strengen.
- Indstil sidste tegn (sidste mellemrum) som
\0
.
Det vigtige er, at funktionen tager markør til markør (streng).Se efter funktionsopkaldet: 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; }
Anvendelse:
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 []