Próbuję napisać idiomatyczną funkcję przycinania w C. Jak to wygląda? Czy powinienem zamiast tego mallocować nowy ciąg i zwrócić go?
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]; } } }
Komentarze
- Tam ' z wieloma problemami z tym kodem, jest on ' podatny na ataki przepełnienia bufora i nie ' rób to, co robi typowa funkcja ” przycinania „. Przycinanie usuwa wiodące i końcowe spacje. To usuwa je wszystkie.
- Dzięki. Czy możesz wyjaśnić, jak radzić sobie z atakami przepełnienia bufora?
- Nigdy nie powinieneś ślepo kopiować danych do jakiegoś bufora, jeśli nie ' nie wiesz, ile miejsca jest przydzielone do niego, że ' prosi tylko o kłopoty. Prostą rzeczą byłoby dodanie parametru, który pobiera rozmiar bufora. W ten sposób to ' leży po stronie dzwoniącego, aby powiedzieć Ci, jak duże jest to naprawdę. Wtedy ' zależy od Ciebie, abyś nigdy nie próbował czytać / pisać więcej niż podana długość. Oczywiście ' nie jest głupim dowodem, dzwoniący może podać fałszywe długości, ale byłby to problem z ich strony, a nie z twoim.
Odpowiedź
Jak zauważył @JeffMercado, usuwa to spacje zamiast skracać spacje wiodące i końcowe. Zakładając, że chcesz zachować obecną funkcjonalność, nazwijmy to remove_spaces
.
Występuje tutaj bardzo subtelny błąd:
isspace
przyjmuje wartość unsigned char lub EOF
. Przekazanie mu char
, który zwykle jest podpisany, spowoduje niezdefiniowane zachowanie. Zamiast tego powiedz:
... isspace((unsigned char) input[i]) ...
Kolejny błąd: nie emitujesz terminatora NUL, co oznacza, że dzwoniący nie miałby możliwości dowiedzieć się, jak długi jest ciąg ( chyba że wyzerował bufor przed wywołaniem funkcji).
Po naprawieniu tych błędów otrzymujemy:
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 również powiedział tę funkcję jest podatny na przepełnienie bufora. W pewnym sensie to nieprawda, pod warunkiem, że dzwoniący wie, że powinien przydzielić bufor o wartości co najmniej strlen(input) + 1
. Jednak dzwoniący może być leniwy i po prostu powiedzieć char result[100]
. Dodanie parametru rozmiaru bufora wyjściowego prawdopodobnie uchroni przed takim błędem:
void remove_spaces(const char *input, char *output, size_t output_size);
Sprawdź, czy możesz to zaimplementować . Kilka rzeczy, o których należy pamiętać:
-
Nie zapomnij o zakończeniu NUL podczas sprawdzania rozmiaru bufora wyjściowego.
-
Nie zachowuj się jak strncpy i pomijaj terminator NUL, gdy musisz skrócić ciąg, ponieważ może to prowadzić do drobnych błędów.
-
Jeśli używasz
int
dlai
ij
isize_t
w przypadkuoutput_size
powinny pojawić się ostrzeżenia kompilatora dotyczące porównania między znakiem i bez znaku. Jeśli tego nie zrobisz, podkręć ostrzeżenia kompilatora. Jeśli używasz GCC z wiersza poleceń, przyzwyczaj się do wpisywaniagcc -Wall -W
.
Komentarze
-
strncpy()
nie jest funkcją ciągową, nawet choć niektórzy zakładają. Tak więc wynik będący ciągiem i tak byłby zdarzeniem. Co sprawia, że analogia jest w najlepszym przypadku szkicowa.
Odpowiedź
Wiemy, że możemy przesuwać wskaźnik do przodu i do tyłu , i wiemy również, że możemy przyciąć ciąg z lewej strony. Jeśli zwiększymy wskaźnik i zmniejszymy wskaźnik, aby przyciąć go od prawej strony, wystarczą dwie while
pętle. Zauważysz, że liczba kroków po prawej stronie jest mniejsza niż liczba kroków po lewej stronie.
Kod prawego przycięcia:
#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"; } }
Odpowiedź
Najłatwiejszy sposób (usuwa tylko spacje):
Przytnij.Start:
- Porównaj znaki do są równe
" "
(spacja lub inne znaki, takie jak\n
lub\t
) na początku ciągu i wzrost zmiennej temp (i
). - Przesuń wskaźnik o
i
(str+=i
). Teraz ciąg zaczyna się od znaku, który nie jest znakiem spacji (ani żadnym innym białym znakiem).
Trim.End:
- Zrób to samo dla Trim.Start, ale od końca ciągu.
- Ustaw ostatni znak (ostatnią spację) jako
\0
.
Ważną rzeczą jest to, że funkcja pobiera wskaźnik do wskaźnika (string).Uważaj na wywołanie funkcji: 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; }
Użycie:
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);
Wynik :
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 []