Yritän kirjoittaa idiomaattista leikkaustoimintoa C. Kuinka tämä näyttää? Pitäisikö minun sen sijaan malloksoida uusi merkkijono ja palauttaa se?
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]; } } }
Kommentit
- Siellä ’ sa useita koodin aiheuttamia ongelmia, se ’ on alttiina puskurin ylivuotohyökkäyksille ja se ei ’ t tee mitä tavallinen ” trim ” -toiminto tekee. Leikkaus poistaa etu- ja takareitit. Tämä riisuu ne kaikki.
- Kiitos. Voitteko kertoa tarkemmin, miten puskurin ylivuotohyökkäyksiä käsitellään?
- Älä koskaan kopioi tietoja sokeasti puskuriin, kun et tiedä ’ et tiedä kuinka paljon tilaa on varattu sille, että ’ kysyy vain ongelmia. Yksinkertainen asia olisi lisätä parametri, joka ottaa puskurin koon. Tällä tavalla ’ soittaa soittajalle kaikki, kuinka suuri se todella on. Sitten ’ riippuu siitä, ettet koskaan yritä lukea / kirjoittaa tietyn pituuden yli. Tietysti se ’ ei ole huijausta, soittaja voi antaa sinulle väärät pituudet, mutta se olisi ongelma heidän, ei sinun.
vastaus
Kuten @JeffMercado huomautti, tämä poistaa välilyöntejä sen sijaan, että leikkaisi etu- ja lopputiloja. Oletetaan, että haluat säilyttää nykyisen toiminnallisuuden, kutsumme sitä nimellä remove_spaces
.
Tässä on todella hienovarainen virhe:
... isspace(input[i]) ...
isspace
ottaa allekirjoittamattoman merkin tai EOF
. Sen välittäminen char
, joka yleensä allekirjoitetaan, tuottaa määrittelemätöntä käyttäytymistä. Sano sen sijaan:
... isspace((unsigned char) input[i]) ...
Toinen virhe: et lähetä NUL-päätelaitetta, mikä tarkoittaa, että soittajalla ei ole mitään tapaa tietää, kuinka kauan merkkijono on ( paitsi jos se nollaa puskurin ennen funktion kutsumista).
Näiden virheiden korjaaminen antaa meille:
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"; }
Myös @JeffMercado sanoi tämän toiminnon on altis puskurin ylivuotolle. Tavallaan tämä ei ole totta, jos soittaja tietää varaavan vähintään strlen(input) + 1
-puskurin. Mutta soittaja saattaa olla laiska ja sanoa vain char result[100]
. Lähtöpuskurin koon parametrin lisääminen estää todennäköisesti tällaisen virheen:
void remove_spaces(const char *input, char *output, size_t output_size);
Katso, voitko toteuttaa tämän Joitakin huomioitavia asioita:
-
Älä unohda NUL-päätettä, kun tarkistat lähtöpuskurin kokoa.
-
Älä ole kuin strncpy ja jätä NUL-pääte pois päältä, kun merkkijono on katkaistava, koska se voi johtaa hienovaraisiin virheisiin.
-
Jos käytät
int
i
jaj
jasize_t
kohteelleoutput_size
, sinun tulisi saada kääntäjän varoitukset allekirjoitettujen ja allekirjoittamattomien vertailusta. Jos et, käännä kääntäjän varoituksia. Jos käytät GCC: tä komentoriviltä, kirjoitagcc -Wall -W
.
kommentit
-
strncpy()
ei ole merkkijono-funktio vaikka jotkut olettavat. Joten merkkijonon tulos olisi joka tapauksessa tapahtuma. Mikä tekee analogiasta parhaimmillaan luonnostavan.
Vastaa
Tiedämme, että voimme siirtää osoitinta eteenpäin ja taaksepäin , ja tiedämme myös, että voimme leikata merkkijonon vasemmalta. Jos kasvatamme osoitinta ja vähennämme osoitinta trimmaamaan oikealta, niin kaksi while
-silmukkaa riittää. Huomaat, että oikean käyntien määrä on pienempi kuin vasemman käyntien määrä.
Oikean reunan koodi:
#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"; } }
Vastaa
Helpoin tapa (poistaa vain välilyönnit):
Trim.Start:
- Vertaa merkkejä ne ovat
" "
(välilyönti tai muut merkit, kuten\n
tai\t
) merkkijonon alussa ja increment temp (i
) -muuttuja. - Siirrä osoitin
i
(str+=i
Trim.End:
- Tee sama kuin Trim.Start-toiminnolle, mutta merkkijonon lopusta.
- Aseta viimeiseksi merkiksi (viimeinen välilyönti)
\0
.
Tärkeää on, että funktio vie osoittimen osoittimeen (merkkijono).Tarkkaile funktion kutsua: 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; }
Käyttö:
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);
Tulos :
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 []