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
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 znakbuf[sizeof(buf)-1] = 0
, aby się przed tym zabezpieczyć, lub jeślibuf
ma wartość zerową, użyjsizeof(buf) - 1
jako długość kopii. - Użyj
strlcpy
lubstrcpy_s
lub nawetsnprintf
jeśli musisz. - Naprawiono. Niestety nie ma łatwego przenośnego sposobu na zrobienie tego, chyba że masz luksus pracy z najnowszymi kompilatorami (
strlcpy
isnprintf
nie są bezpośrednio dostępne na MSVC przynajmniej zamówienia istrcpy_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 zamiaststrlen
, gdy ' martwisz się o pamięć. Podobnie możesz sprawdzić przed wywołaniemstrcpy
, 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ę nachar*
, 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. Nawetstring[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? Achar *
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ówchar[]
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ówchar* 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ć jakoconst char *s = "literal";
- ” Ogólnie rzecz biorąc, ” asdf ” jest stałą, więc powinno być zadeklarowane jako const. ” – to samo rozumowanie wymagałoby
const
naint 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óra1 + 1
daje wynik2
. Jeśli program, do którego utworzyłem łącze powyżej robi coś innego niż drukowanieEFGH
, 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łą).
strncpy
rzadko jest właściwą odpowiedzią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 zseprate
doseparate
zepsuje się, jeśli rozmiar nie zostanie zaktualizowany.strlen()
nie zawiera znaku null, używając , aby pomieścić maksymalny rozmiar wymagany dlachar string[]
często wygląda źle. IMO,MAX_MONTH_SIZE
byłoby lepsze tutaj.