Czy inicjowanie znaku [] za pomocą łańcucha znaków jest złą praktyką?

Czytałem wątek zatytułowany „strlen vs sizeof” na CodeGuru i jedna z odpowiedzi stwierdza, że „tak czy inaczej [sic] jest złą praktyką przy inicjowaniu [sic] tablicy char z literałem ciągu. ”

Czy to prawda, czy to tylko jego opinia (aczkolwiek„ elitarnego członka ”)?


Oto oryginalne pytanie:

#include <stdio.h> #include<string.h> main() { char string[] = "october"; strcpy(string, "september"); printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string)); return 0; } 

prawda. Rozmiar powinien być długością plus 1 tak?

to jest wynik

the size of september is 8 and the length is 9

rozmiar powinien z pewnością wynosić 10. To tak, jak obliczanie rozmiaru ciągu przed jego zmianą przez strcpy, ale długość po.

Czy coś jest nie tak z moją składnią, czy co?


Oto odpowiedź :

I tak jest złą praktyką inicjowanie tablicy znaków literałem ciągu. Dlatego zawsze wykonaj jedną z następujących czynności:

const char string1[] = "october"; char string2[20]; strcpy(string2, "september"); 

Komentarze

  • Zwróć uwagę na ” const ” w pierwszym wierszu. Czy to możliwe, że autor założył c ++ zamiast c? W c ++ jest to ” zła praktyka „, ponieważ literał powinien być const, a każdy najnowszy kompilator C ++ wyświetli ostrzeżenie (lub błąd) o przypisywaniu literału const do tablicy innej niż const.
  • @Andr é C ++ definiuje literały ciągów jako tablice const, ponieważ jest to jedyny bezpieczny sposób postępowania z nimi. To, że C nie ' t jest problemem, więc masz regułę społeczną, która wymusza bezpieczeństwo
  • @Caleth. Wiem, bardziej starałem się argumentować, że autor odpowiedzi podchodził do ” złych praktyk ” z perspektywy C ++.
  • @Andr é to nie jest ' to zła praktyka w C ++, ponieważ nie jest ' ta praktyka , to ' to zwykły błąd typu. Powinien to być błąd typu w C, ale nie jest to ' t, więc musisz mieć regułę przewodnika po stylu, która mówi Ci ” To ' s zabronione ”

Odpowiedź

Inicjowanie tablicy znaków za pomocą literału ciągu jest zawsze złą praktyką.

Autor tego komentarza nigdy go tak naprawdę nie usprawiedliwia, a stwierdzenie to wydaje mi się zagadkowe.

W C (a ty oznaczyłeś to jako C), że ” jest właściwie jedynym sposobem zainicjowania tablicy char z wartością ciągu (inicjalizacja różni się od przypisania). Możesz napisać albo

char string[] = "october"; 

lub

char string[8] = "october"; 

lub

char string[MAX_MONTH_LENGTH] = "october"; 

W pierwszym przypadku rozmiar tablicy jest pobierany z rozmiaru inicjatora. Literały ciągów są przechowywane jako tablice char kończącym bajtem 0, więc rozmiar tablicy wynosi 8 („o”, „c”, „t”, „o”, „b”, „e”, „r”, 0). W pozostałych dwóch przypadkach rozmiar tablicy jest określany jako część deklaracji (8 i MAX_MONTH_LENGTH, cokolwiek to jest).

To, czego nie możesz zrobić, to napisać coś takiego jak

char string[]; string = "october"; 

lub

char string[8]; string = "october"; 

itd. W pierwszym przypadku deklaracja string jest niekompletna , ponieważ nie określono rozmiaru tablicy i nie ma inicjatora, z którego można by pobrać rozmiar. W obu W przypadkach = nie zadziała, ponieważ a) wyrażenie tablicowe, takie jak string, może nie być celem przypisania oraz b) Operator = i tak nie jest zdefiniowany do kopiowania zawartości jednej tablicy do drugiej.

W ten sam sposób nie można pisać

char string[] = foo; 

gdzie foo to kolejna tablica char. Ta forma inicjalizacji będzie działać tylko z literałami łańcuchowymi.

EDYTUJ

Powinienem to zmienić, aby powiedzieć, że możesz również zainicjować tablice do przechowywania łańcucha z inicjatorem w stylu tablicy, na przykład

char string[] = {"o", "c", "t", "o", "b", "e", "r", 0}; 

lub

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII 

ale łatwiej jest używać literałów tekstowych.

EDYTUJ 2

Aby przypisać zawartość tablicy poza deklaracją, musiałbyś użyć albo strcpy/strncpy (dla ciągów zakończonych 0) lub memcpy (dla dowolnego innego typu tablicy):

if (sizeof string > strlen("october")) strcpy(string, "october"); 

lub

strncpy(string, "october", sizeof string); // only copies as many characters as will // fit in the target buffer; 0 terminator // may not be copied, but the buffer is // uselessly completely zeroed if the // string is shorter! 

Komentarze

  • strncpy rzadko jest właściwą odpowiedzią
  • @KeithThompson: nie zgadzam się, po prostu dodałem to dla kompletności ' sake.
  • Pamiętaj, że char[8] str = "october"; to zła praktyka. Musiałem się policzyć dosłownie, aby upewnić się, że ' nie jest przepełnieniem i psuje się podczas konserwacji … np. poprawienie błędu pisowni z seprate do separate zepsuje się, jeśli rozmiar nie zostanie zaktualizowany.
  • Zgadzam się z djechlin, to jest złą praktyką z podanych powodów. JohnBode ' nie ' nie komentuje ” złych praktyk ” aspekt (który jest główną częścią pytania !!), wyjaśnia tylko, co możesz, a czego nie możesz zrobić, aby zainicjować tablicę.
  • Drobne: Ponieważ ' length ” wartość zwrócona z strlen() nie zawiera znaku null, używając , aby pomieścić maksymalny rozmiar wymagany dla char string[] często wygląda źle. IMO, MAX_MONTH_SIZE byłoby lepsze tutaj.

Odpowiedź

Jedyny problem, jaki sobie przypominam, to przypisanie literału ciągu do char *:

char var1[] = "september"; var1[0] = "S"; // Ok - 10 element char array allocated on stack char const *var2 = "september"; var2[0] = "S"; // Compile time error - pointer to constant string char *var3 = "september"; var3[0] = "S"; // Modifying some memory - which may result in modifying... something or crash 

Na przykład weź ten program:

#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); } 

To na mojej platformie (Linux) ulega awarii podczas próby zapisu na stronie oznaczonej jako tylko do odczytu. Na innych platformach może wydrukować „wrzesień” itp.

To powiedziawszy – inicjalizacja dosłownie powoduje określoną liczbę rezerwacji, więc to nie zadziała:

char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep" 

Ale to będzie

char buf[32] = "May"; strncpy(buf, "September", sizeof(buf)); 

Jako ostatnia uwaga – nie użyłbym strcpy w ogóle:

char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory 

Chociaż niektóre kompilatory mogą zmienić to na bezpieczne wywołanie, strncpy jest znacznie bezpieczniejsze:

char buf[1024]; strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers. buf[sizeof(buf) - 1] = "\0"; 

Komentarze

  • Tam ' s nadal ryzyko przepełnienia bufora na tym strncpy, ponieważ nie ' t null przerywa kopiowany ciąg, gdy długość something_else jest większe niż sizeof(buf). Zwykle ustawiam ostatni znak buf[sizeof(buf)-1] = 0, aby się przed tym zabezpieczyć, lub jeśli buf ma wartość zerową, użyj sizeof(buf) - 1 jako długość kopii.
  • Użyj strlcpy lub strcpy_s lub nawet snprintf jeśli musisz.
  • Naprawiono. Niestety nie ma łatwego przenośnego sposobu na zrobienie tego, chyba że masz luksus pracy z najnowszymi kompilatorami (strlcpy i snprintf nie są bezpośrednio dostępne na MSVC przynajmniej zamówienia i strcpy_s nie są na * nix).
  • @MaciejPiechotka: Cóż, dzięki Bogu Unix odrzucił sponsorowany przez firmę Microsoft załącznik k.

Odpowiedź

Przede wszystkim dlatego, że nie masz rozmiaru char[] w zmiennej / konstrukcji, której można łatwo użyć w programie.

Przykładowy kod z linku:

 char string[] = "october"; strcpy(string, "september"); 

Element string jest przydzielany na stosie jako długość 7 lub 8 znaków. Nie pamiętam, czy jest zakończony zerem w ten sposób, czy nie – wątek, do którego utworzyłeś łącze, stwierdził, że jest .

Kopiowanie „september” przez ten ciąg jest oczywistym przepełnieniem pamięci.

Kolejne wyzwanie pojawia się, jeśli przekażesz string do innej funkcjiwięc druga funkcja może zapisywać w tablicy. Musisz powiedzieć drugiej funkcji, jak długa jest tablica, aby aby nie spowodowała przekroczenia. Możesz przekazać string razem z wynikiem strlen(), ale wątek wyjaśnia, jak to może się wydostać, jeśli string nie jest zakończone zerem.

Lepiej Ci będzie przydzielanie ciągu o stałym rozmiarze (najlepiej zdefiniowanym jako stała), a następnie przekazanie tablicy i stałego rozmiaru do innej funkcji. Komentarze @John Bode są poprawne i istnieją sposoby na złagodzenie tego ryzyka. Korzystanie z nich wymaga również większego wysiłku z Twojej strony.

Z mojego doświadczenia wynika, że zainicjowałem wartość char[] to jest zwykle za małe dla innych wartości, które muszę tam umieścić. Użycie zdefiniowanej stałej pomaga uniknąć tego problemu.


sizeof string poda rozmiar bufora (8 bajtów); użyj wyniku tego wyrażenia zamiast strlen, gdy „martwisz się o pamięć”.
Podobnie możesz sprawdzić przed wywołaniem strcpy, aby sprawdzić, czy bufor docelowy jest wystarczająco duży dla ciągu źródłowego: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Tak, jeśli musisz przekazać tablicę do funkcji, należy również przekazać jego fizyczny rozmiar: foo (array, sizeof array / sizeof *array);. – John Bode

Komentarze

  • sizeof string poda rozmiar bufora (8 bajtów); użyj wyniku tego wyrażenia zamiast strlen, gdy ' martwisz się o pamięć. Podobnie możesz sprawdzić przed wywołaniem strcpy, aby sprawdzić, czy bufor docelowy jest wystarczająco duży dla ciągu źródłowego: if (sizeof target > strlen(src)) { strcpy (target, src); }. Tak, jeśli musisz przekazać tablicę do funkcji, ' musisz również przekazać jej rozmiar fizyczny: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – dzięki, i to są dobre strony. Włączyłem Twój komentarz do mojej odpowiedzi.
  • Dokładniej, większość odniesień do nazwy tablicy string powoduje niejawną konwersję na char*, wskazując na pierwszy element tablicy. Spowoduje to utratę informacji o granicach tablicy. Wywołanie funkcji to tylko jeden z wielu kontekstów, w których to się dzieje. char *ptr = string; to kolejna. Nawet string[0] jest tego przykładem; operator [] działa na wskaźnikach, a nie bezpośrednio na tablicach. Sugerowana lektura: Sekcja 6 najczęściej zadawanych pytań comp.lang.c .
  • Na koniec odpowiedź, która faktycznie odnosi się do pytania!

Odpowiedź

Jedna rzecz, której żaden wątek nie porusza, to:

char whopping_great[8192] = "foo"; 

kontra

char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo")); 

Pierwsza z nich zrobi coś takiego:

memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo")); 

Ten ostatni robi tylko memcpy. Standard C mówi, że jeśli inicjalizowana jest jakakolwiek część tablicy, to wszystko tak jest. Więc w tym przypadku lepiej zrobić to samemu. Myślę, że to mógł być cel treuss.

Na pewno

char whopping_big[8192]; whopping_big[0] = 0; 

jest lepsze niż:

char whopping_big[8192] = {0}; 

lub

char whopping_big[8192] = ""; 

ps Dla dodatkowe punkty, możesz zrobić:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo")); 

aby wyrzucić błąd kompilacji podzielony przez zero, jeśli masz zamiar przepełnić tablicę.

Odpowiedź

Myślę, że idea „złej praktyki” wynika z faktu, że ten formularz:

char string[] = "october is a nice month"; 

niejawnie tworzy strcpy ze źródłowego kodu maszynowego do stosu.

Bardziej wydajne jest obsługiwanie tylko linku do tego ciągu. Jak w przypadku:

char *string = "october is a nice month"; 

lub bezpośrednio:

strcpy(output, "october is a nice month"); 

(ale oczywiście w większości kod to prawdopodobnie nie ma znaczenia)

Komentarze

  • Nie ' t to tylko kopiowanie jeśli spróbujesz go zmodyfikować? Myślę, że kompilator byłby mądrzejszy
  • A co z przypadkami takimi jak char time_buf[] = "00:00";, gdzie ' czy zamierzasz modyfikować bufor? A char * zainicjowany literałem ciągu jest ustawiany na adres pierwszego bajtu, więc próba zmodyfikowania tego skutkuje niezdefiniowanym zachowaniem, ponieważ metoda literału ciągu ' jest nieznana (zdefiniowana w implementacji), podczas gdy modyfikowanie bajtów char[] jest całkowicie legalne, ponieważ inicjalizacja kopiuje bajty do zapisywalnego miejsca przydzielonego na stosie. Aby powiedzieć, że ' s ” mniej wydajne lub ” złe postępowanie ” bez omawiania niuansów char* vs char[] jest mylące.

Odpowiedź

Nigdy nie jest naprawdę długo, ale należy unikać inicjalizacji znaku [] do łańcucha, ponieważ „string” to const char * i przypisujesz go do char *. Więc jeśli przekażesz ten znak [] do metody, która zmienia dane, możesz mieć interesujące zachowanie.

Jak powiedział komenda, zmieszałem trochę char [] z char *, to nie jest dobre, ponieważ one trochę się różnią.

Nie ma nic złego w przypisywaniu danych do tablicy char, ale ponieważ intencją użycia tej tablicy jest użycie jej jako „string” (char *), łatwo zapomnieć, że nie powinieneś tego modyfikować tablica.

Komentarze

  • Niepoprawne. Inicjalizacja kopiuje zawartość literału ciągu do tablicy. Obiekt tablicy to n ' t const, chyba że zdefiniujesz to w ten sposób.(A literały ciągów w C nie są const, chociaż każda próba zmodyfikowania literału ciągu ma niezdefiniowane zachowanie.) char *s = "literal"; ma rodzaj zachowania, o którym ' mówisz; ' lepiej napisać jako const char *s = "literal";
  • ” Ogólnie rzecz biorąc, ” asdf ” jest stałą, więc powinno być zadeklarowane jako const. ” – to samo rozumowanie wymagałoby const na int n = 42;, ponieważ 42 jest stałą.
  • Nie ' nie ma znaczenia, na jakiej maszynie ' jest włączony. Standard języka gwarantuje, że c można modyfikować. To ' jest dokładnie tak samo silną gwarancją, jak ta, która 1 + 1 daje wynik 2. Jeśli program, do którego utworzyłem łącze powyżej robi coś innego niż drukowanie EFGH, oznacza to niezgodną implementację C.
  • @Dainus: kompilator MSVC ma optymalizację o nazwie ' pule ciągów ', która umieści pojedynczą kopię identyczne ciągi do segmentu tylko do odczytu, jeśli może zagwarantować, że ich użycie jest tylko do odczytu. Wyłącz optymalizację, aby zobaczyć ' normalne ' zachowanie. Do Twojej wiadomości, ” Edytuj i kontynuuj ” wymaga, aby ta opcja była włączona. Więcej informacji tutaj: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Myślę, że Dainius sugeruje, że w wielu przypadkach błąd polega na tym, że sama zmienna powinna być oznaczona jako const char *const, aby zapobiec modyfikowaniu bajtów lub samego wskaźnika, ale w wielu przypadkach programiści zostawiają jedną lub obie zmienne, pozwalając na kod czasu wykonania zmodyfikować coś, co wygląda na stałą wpisaną (ale nie jest stałą).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *