Czytałem wątek zatytułowany „strlen vs sizeof” na CodeGuru , a jedna z odpowiedzi mówi, że „w każdym razie [sic] złą praktyką jest inicjowanie [sic] char
tablicy z dosłownym ciągiem znaków”.
Czy to prawda, czy to tylko jego (choć „elitarny członek”) opinia?
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;
}
dobrze. rozmiar powinien być równy 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 jak obliczanie rozmiaru ciągu przed zmianą przez strcpy, ale długość po nim.
Czy coś jest nie tak z moją składnią?
Oto odpowiedź :
W każdym razie złą praktyką jest inicjałowanie tablicy znaków literałem łańcuchowym. Dlatego zawsze wykonaj jedną z następujących czynności:
const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
c
programming-practices
strings
array
Cole Johnson
źródło
źródło
Odpowiedzi:
Autor tego komentarza tak naprawdę nigdy go nie usprawiedliwia, a stwierdzenie to mnie zastanawia.
W C (i oznaczyłeś to jako C), jest to prawie jedyny sposób na zainicjowanie tablicy
char
z wartością ciągu (inicjalizacja różni się od przypisania). Możesz napisać albolub
lub
W pierwszym przypadku rozmiar tablicy jest pobierany z rozmiaru inicjatora. Literały łańcuchowe są przechowywane jako tablice
char
z końcowym bajtem 0, więc rozmiar tablicy wynosi 8 („o”, „c”, „t”, „o”, „b”, „e”, „r”, 0). W dwóch pozostałych przypadkach rozmiar tablicy jest określony jako część deklaracji (8 iMAX_MONTH_LENGTH
, cokolwiek by się nie zdarzyło).Czego nie może zrobić, to napisać coś
lub
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 przypadkach=
nie zadziała, ponieważ a) wyrażenie tablicowe, którestring
może nie być celem zadania ib)=
operator nie jest zdefiniowany tak, aby kopiować zawartość jednej tablicy do drugiej.Z tego samego powodu nie możesz pisać
gdzie
foo
jest kolejna tablicachar
. Ta forma inicjalizacji będzie działać tylko z literałami łańcuchowymi.EDYTOWAĆ
Powinienem to zmienić, aby powiedzieć, że możesz także inicjować tablice, aby przechowywać ciąg znaków za pomocą inicjatora w stylu tablicowym
lub
ale łatwiej jest oczom używać literałów łańcuchowych.
EDYCJA 2
Aby przypisać zawartość tablicy poza deklaracją, musisz użyć albo
strcpy/strncpy
(dla łańcuchów zakończonych 0) lubmemcpy
(dla dowolnego innego typu tablicy):lub
źródło
strncpy
rzadko jest właściwą odpowiedziąchar[8] str = "october";
jest to zła praktyka. Musiałem dosłownie zwęglić się, aby upewnić się, że nie jest to przepełnienie i że psuje się podczas konserwacji ... np. Poprawianie błędu pisowni odseprate
do zepsuje się,separate
jeśli rozmiar nie zostanie zaktualizowany.strlen()
nie zawiera znaku zerowego, użycieMAX_MONTH_LENGTH
do utrzymania maksymalnego rozmiaru wymaganegochar string[]
często wygląda źle. IMO,MAX_MONTH_SIZE
byłoby lepiej tutaj.Jedyny problem, jaki pamiętam, to przypisanie literału ciągów do
char *
:Na przykład weź ten program:
To na mojej platformie (Linux) ulega awarii podczas próby zapisu na stronie oznaczonej jako tylko do odczytu. Na innych platformach może drukować „wrzesień” itp.
To powiedziawszy - inicjalizacja przez literał powoduje określoną liczbę rezerwacji, więc to nie zadziała:
Ale to będzie
Ostatnia uwaga - w ogóle nie używałbym
strcpy
:Chociaż niektóre kompilatory mogą zmienić to w bezpieczne wywołanie,
strncpy
jest o wiele bezpieczniejsze:źródło
strncpy
ponieważ nie kończy on kopiowanego łańcucha, gdy długośćsomething_else
jest większa niżsizeof(buf)
. Zwykle ustawiam ostatni znak,buf[sizeof(buf)-1] = 0
aby go zabezpieczyć, lub jeślibuf
jest inicjowany na zero, użyjsizeof(buf) - 1
jako długości kopii.strlcpy
lubstrcpy_s
nawetsnprintf
jeśli musisz.strlcpy
isnprintf
nie jesteś bezpośrednio dostępny w MSVC, przynajmniej zamówienia istrcpy_s
nie ma * nix).Jedną rzeczą, której nie porusza żaden wątek, jest to:
vs.
Ten pierwszy zrobi coś takiego:
Ten ostatni robi tylko memcpy. Standard C nalega, aby inicjalizować dowolną część tablicy, wszystko jest w porządku. W takim przypadku lepiej to zrobić samemu. Wydaje mi się, że o to właśnie chodziło.
Na pewno
jest lepszy niż:
lub
ps Aby uzyskać punkty bonusowe, możesz:
aby rzucić czas kompilacji podziel przez błąd zero, jeśli masz zamiar przepełnić tablicę.
źródło
Przede wszystkim dlatego, że nie będziesz mieć wielkości
char[]
zmiennej / konstrukcji, której można łatwo użyć w programie.Przykładowy kod z linku:
string
jest przydzielany na stosie jako 7 lub 8 znaków. Nie mogę sobie przypomnieć, czy jest zakończone zerem w ten sposób, czy nie - wątek, do którego linkujesz, stwierdził, że tak.Kopiowanie „września” nad tym ciągiem jest oczywistym przepełnieniem pamięci.
Kolejne wyzwanie pojawia się, jeśli przejdziesz
string
do innej funkcji, aby inna funkcja mogła zapisać się w tablicy. Trzeba powiedzieć, że inne funkcje, jak długo tablica jest tak , że nie tworzy przepełnienie. Możesz przekazaćstring
wraz z wynikiem,strlen()
ale wątek wyjaśnia, jak to może wybuchnąć, jeślistring
nie jest zakończone zerem.Lepiej jest przydzielić ciąg znaków o stałym rozmiarze (najlepiej zdefiniowanym jako stała), a następnie przekazać tablicę i stały rozmiar innej funkcji. Komentarze Johna Bode'a są poprawne i istnieją sposoby na ograniczenie tego ryzyka. Wymagają również więcej wysiłku z Twojej strony, aby z nich korzystać.
Z mojego doświadczenia
char[]
wynika , że wartość, którą zainicjowałem, jest zwykle zbyt mała dla innych wartości, które muszę tam umieścić. Korzystanie ze zdefiniowanej stałej pomaga uniknąć tego problemu.sizeof string
poda rozmiar bufora (8 bajtów); użyj wyniku tego wyrażenia zamiaststrlen
gdy martwisz się pamięcią.Podobnie można wykonać test przed wywołanie
strcpy
aby sprawdzić, czy bufor docelowy jest wystarczająco duży dla łańcucha źródłowego:if (sizeof target > strlen(src)) { strcpy (target, src); }
.Tak, jeśli masz przekazać tablicę do funkcji, musisz przekazać swoje fizyczne wymiary, a także:
foo (array, sizeof array / sizeof *array);
. - John Bodeźródło
sizeof string
poda rozmiar bufora (8 bajtów); użyj wyniku tego wyrażenia zamiaststrlen
gdy martwisz się pamięcią. Podobnie można wykonać test przed wywołaniestrcpy
aby sprawdzić, czy bufor docelowy jest wystarczająco duży dla łańcucha źródłowego:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Tak, jeśli masz przekazać tablicę do funkcji, musisz przekazać swoje fizyczne wymiary, a także:foo (array, sizeof array / sizeof *array);
.string
powoduje niejawną konwersję nachar*
, wskazując na pierwszy element tablicy. To powoduje utratę informacji o granicach tablicy. Wywołanie funkcji jest tylko jednym z wielu kontekstów, w których tak się dzieje.char *ptr = string;
jest inny. Nawetstring[0]
jest tego przykładem;[]
operator pracuje nad wskaźnikami, a nie bezpośrednio na macierzach. Sugerowana literatura: Sekcja 6 comp.lang.c FAQ .Myślę, że pomysł „złej praktyki” wynika z faktu, że ta forma:
czyni domyślnie strcpy ze źródłowego kodu maszynowego na stos.
Bardziej wydajne jest obsługiwanie tylko linku do tego ciągu. Podobnie jak w przypadku:
lub bezpośrednio:
(ale oczywiście w większości kodów to chyba nie ma znaczenia)
źródło
char time_buf[] = "00:00";
których będziesz modyfikować bufor?char *
Inicjowane ciągiem znaków jest ustawiona na adres pierwszego bajtu, więc próbuje modyfikować wyniki w niezdefiniowanej zachowań, ponieważ metoda przechowywania łańcuchem znaków jest nieznany (wdrożenie zdefiniowane), natomiast modyfikując bajtów: achar[]
to dlatego, że całkowicie legalne inicjalizacja kopiuje bajty do miejsca do zapisu przydzielonego na stosie. Stwierdzenie, że jest to „mniej wydajne” lub „zła praktyka” bez rozwijania niuansów,char* vs char[]
jest mylące.Nigdy nie jest naprawdę długi czas, ale należy unikać inicjalizacji char [] do string, ponieważ „string” jest const char *, a ty przypisujesz go do char *. Więc jeśli przekażesz ten znak [] metodzie, która zmienia dane, możesz mieć ciekawe zachowanie.
Jak powiedział komenda, wymieszałem trochę char [] z char *, to nie jest dobre, ponieważ się trochę 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 „łańcucha” (char *), łatwo zapomnieć, że nie należy modyfikować tej tablicy.
źródło
const
chyba że zdefiniujesz go w ten sposób. (A literały łańcuchowe w C nie sąconst
, chociaż każda próba modyfikacji literału łańcuchowego ma nieokreślone zachowanie.)char *s = "literal";
Ma takie zachowanie, o którym mówisz; lepiej napisać jakoconst char *s = "literal";
const
włączeniaint n = 42;
, ponieważ42
jest stałe.c
można go modyfikować. To dokładnie tak silna gwarancja, jak ta, która1 + 1
ocenia2
. Jeśli program, do którego się przyłączyłem, robi coś innego niż drukowanieEFGH
, oznacza to niezgodną implementację C.