Jag försöker skriva en idiomatisk trimfunktion i C. Hur ser det ut? Ska jag istället mallocera den nya strängen och returnera 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
- Det ’ ett antal problem med den koden, det ’ är sårbart för attacker med buffertöverflöde och det ’ t gör vad en typisk ” trim ” -funktion. Trim tar bort ledande och efterföljande mellanslag. Detta tar bort dem alla.
- Tack. Kan du snälla utarbeta hur man hanterar buffertöverskridningsattacker?
- Du ska aldrig blint kopiera data till någon buffert när du inte ’ inte vet hur mycket utrymme som tilldelas till det, att ’ bara ber om problem. En enkel sak att göra är att lägga till en parameter som tar in buffertens storlek. På det sättet ’ är alla som ringer för att berätta hur stor den verkligen är. Sedan är det ’ upp till dig att aldrig försöka läsa / skriva utöver den angivna längden. Självklart är det ’ inte dåligt bevis, den som ringer kan ge dig falska längder, men det skulle vara ett problem i deras ände, inte din.
Svar
Som @JeffMercado påpekade tar detta bort mellanslag istället för att trimma ledande och efterföljande mellanslag. Förutsatt att du vill behålla den aktuella funktionen, låt oss kalla det remove_spaces
.
Det finns ett riktigt subtilt fel här:
... isspace(input[i]) ...
isspace
tar värdet av en osignerad karaktär eller EOF
. Att skicka en char
, som vanligtvis är signerad, kommer att ge odefinierat beteende. Säg istället:
... isspace((unsigned char) input[i]) ...
Ytterligare ett fel: du släpper inte ut en NUL-terminator, vilket innebär att den som ringer inte skulle kunna veta hur lång strängen är ( om det inte nollställt bufferten innan du ringer till din funktion).
Att fixa dessa buggar ger 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 också att den här funktionen är sårbart för buffertöverskridande. På sätt och vis är detta inte sant, förutsatt att den som ringer vet att tilldela en buffert på minst strlen(input) + 1
. Men den som ringer kan vara lat och bara säga char result[100]
. Att lägga till en parameter för utmatningsbuffertstorlek skyddar troligen mot ett sådant misstag:
void remove_spaces(const char *input, char *output, size_t output_size);
Se om du kan implementera detta . Några saker att tänka på:
-
Glöm inte NUL-terminatorn när du kontrollerar utmatningsbuffertstorleken.
-
Var inte som strncpy och utelämna NUL-terminatorn när du måste stänga av strängen, eftersom det kan leda till subtila buggar.
-
Om du använder
int
föri
ochj
ochsize_t
föroutput_size
bör du få kompilatorvarningar om jämförelse mellan signerad och osignerad. Om du inte gör det, skruva upp kompileringsvarningarna. Om du använder GCC från kommandoraden bör du vana att skrivagcc -Wall -W
.
Kommentarer
-
strncpy()
är inte en strängfunktion, även även om vissa antar. Så resultatet att en sträng skulle vara i alla fall. Vilket gör analogin i bästa fall sketchy.
Svar
Vi vet att vi kan flytta en pekare framåt och bakåt , och vi vet också att vi kan trimma en sträng från vänster. Om vi ökar pekaren och minskar pekaren för att trimma från höger, räcker det med två while
öglor. Du kommer att märka att antalet högra gångar är mindre än antalet vänstra gångar.
Högerklippskod:
#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
Enklast sätt (tar bara bort mellanslag):
Trim.Start:
- Jämför tecken tills de är lika med
" "
(mellanslag eller andra tecken som\n
eller\t
) vid strängens start och stegvis temp (i
) variabel. - Flytta pekaren om
i
(str+=i
). Nu börjar sträng från char som inte är ett mellanslag (eller någon annan vit char).
Trim.End:
- Gör samma sak för Trim.Start men från slutet av strängen.
- Ställ in sista tecknet (sista mellanslaget) som
\0
.
Det viktiga är att funktionen tar pekaren till pekaren (sträng).Se upp för funktionsanropet: 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; }
Användning:
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 []