Ik probeer een idiomatische trimfunctie in C te schrijven. Hoe ziet dit eruit? Moet ik in plaats daarvan de nieuwe string verkeerd plaatsen en deze teruggeven?
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]; } } }
Reacties
- Daar ‘ is een aantal problemen met die code, het ‘ is kwetsbaar voor bufferoverloopaanvallen en het ‘ doe niet wat een typische ” trim ” functie doet. Trim verwijdert voorloop- en volgspaties. Dit verwijdert ze allemaal.
- Bedankt. Kun je alsjeblieft uitleggen hoe om te gaan met bufferoverloopaanvallen?
- Je mag nooit blindelings gegevens naar een buffer kopiëren als je niet weet hoeveel ruimte er is toegewezen ‘ dat ‘ alleen maar om problemen vraagt. Een eenvoudig ding om te doen zou zijn om een parameter toe te voegen die de grootte van de buffer aanneemt. Op die manier is het ‘ allemaal op de beller gericht om u te vertellen hoe groot het werkelijk is. Vervolgens is het aan jou ‘ om nooit te proberen te lezen / schrijven buiten de opgegeven lengte. Het ‘ is natuurlijk niet onfeilbaar, de beller kan je neppe lengtes geven, maar dat zou een probleem zijn aan hun kant, niet aan de jouwe.
Answer
Zoals @JeffMercado opmerkte, verwijdert dit spaties in plaats van voorloop- en volgspaties bij te snijden. Ervan uitgaande dat u de huidige functionaliteit wilt behouden, laten we het dan remove_spaces
noemen.
Er zit hier een heel subtiele bug:
... isspace(input[i]) ...
isspace
heeft de waarde van een niet-ondertekend teken of EOF
. Het doorgeven van een char
, die meestal is ondertekend, zal ongedefinieerd gedrag produceren. Zeg in plaats daarvan:
... isspace((unsigned char) input[i]) ...
Nog een bug: je zendt geen NUL-terminator uit, wat betekent dat de beller niet kan weten hoe lang de string is ( tenzij het de buffer op nul heeft gezet voordat je functie wordt aangeroepen).
Het oplossen van deze bugs geeft ons:
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 zei ook dat deze functie is kwetsbaar voor bufferoverloop. In zekere zin is dit niet waar, op voorwaarde dat de beller weet dat hij een buffer moet toewijzen van ten minste strlen(input) + 1
. Maar de beller is misschien lui en zegt gewoon char result[100]
. Het toevoegen van een parameter voor de grootte van de uitvoerbuffer zal waarschijnlijk beschermen tegen een dergelijke fout:
void remove_spaces(const char *input, char *output, size_t output_size);
Kijk of je dit kunt implementeren . Enkele dingen om in gedachten te houden:
-
Vergeet de NUL-terminator niet bij het controleren van de uitvoerbuffergrootte.
-
Wees niet zoals strncpy en laat de NUL-terminator weg als je de string moet afkappen, aangezien dit kan leiden tot subtiele bugs.
-
Als je
int
gebruikt voori
enj
ensize_t
vooroutput_size
, zou u compileerwaarschuwingen moeten krijgen over vergelijking tussen ondertekend en niet-ondertekend. Als u dit niet doet, zet dan uw compilerwaarschuwingen hoger. Als u GCC gebruikt vanaf de opdrachtregel, maak er dan een gewoonte van omgcc -Wall -W
te typen.
Reacties
-
strncpy()
is geen string-functie, zelfs hoewel sommigen aannemen. Dus het resultaat dat een string is, zou hoe dan ook toeval zijn. Dat maakt de analogie op zijn best vaag.
Antwoord
We weten dat we een aanwijzer vooruit en achteruit kunnen bewegen , en we weten ook dat we een touwtje van links kunnen knippen. Als we de aanwijzer verhogen en verlagen om van rechts af te snijden, zijn twee while
lussen voldoende. U zult opmerken dat het aantal wandelingen naar rechts minder is dan het aantal links lopen.
Code voor rechtsknippen:
#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"; } }
Answer
Gemakkelijkste manier (verwijdert alleen spaties):
Trim.Start:
- Tekens vergelijken tot ze zijn gelijk aan
" "
(spatie of andere tekens zoals\n
of\t
) aan het begin van de tekenreeks en verhoog de temp (i
) variabele. - Verplaats de aanwijzer over
i
(str+=i
). String begint nu met char dat geen spatie is (of een ander wit teken).
Trim.End:
- Doe hetzelfde voor Trim.Start maar vanaf het einde van de string.
- Stel last char (laatste spatie) in als
\0
.
Het belangrijkste is dat de functie pointer naar pointer (string) brengt.Let op de functieaanroep: 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; }
Gebruik:
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);
Resultaat :
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 []