Łańcuch AC to tablica znaków, która kończy się zerowym terminatorem .
Wszystkie znaki mają wartość tabeli symboli. Terminator zerowy to wartość symbolu 0
(zero). Służy do oznaczania końca łańcucha. Jest to konieczne, ponieważ rozmiar łańcucha nie jest nigdzie przechowywany.
Dlatego za każdym razem, gdy przydzielasz miejsce dla ciągu, musisz uwzględnić wystarczającą ilość miejsca na znak zerowy terminatora. Twój przykład tego nie robi, przydziela miejsce tylko dla 5 znaków "hello"
. Prawidłowy kod powinien być:
char str[6] = "hello";
Lub równoważnie, możesz napisać samodokumentujący kod dla 5 znaków plus 1 zerowy terminator:
char str[5+1] = "hello";
Podczas dynamicznego przydzielania pamięci dla ciągu w czasie wykonywania należy również przydzielić miejsce dla terminatora zerowego:
char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);
Jeśli nie dodasz terminatora zerowego na końcu łańcucha, funkcje biblioteczne oczekujące, że łańcuch nie będzie działał poprawnie, a otrzymasz błędy „niezdefiniowanego zachowania”, takie jak wyrzucanie elementów bezużytecznych lub awarie programu.
Najczęstszym sposobem napisać znak zerowy terminatora w C jest za pomocą tzw „ósemkowy sekwencję escape”, patrząc jak ten: '\0'
. Jest to w 100% ekwiwalent zapisu 0
, ale \
służy jako kod samokontrujący do stwierdzenia, że zero ma jawnie oznaczać zerowy terminator. Kod taki jak if(str[i] == '\0')
sprawdza, czy określony znak jest terminatorem zerowym.
Pamiętaj, że termin terminator null nie ma nic wspólnego ze wskaźnikami null ani NULL
makrem! Może to być mylące - bardzo podobne nazwy, ale bardzo różne znaczenia. Dlatego terminator zerowy jest czasami określany jako NUL
jeden L, nie należy go mylić ze NULL
wskaźnikami zerowymi. Aby uzyskać szczegółowe informacje, zobacz odpowiedzi na to SO pytanie .
W "hello"
twoim kodzie nazywa się literał łańcuchowy . Należy to traktować jako ciąg tylko do odczytu. Te ""
środki składniowe, że kompilator dołączy null terminator na końcu łańcucha dosłowne automatycznie. Więc jeśli wydrukujesz, sizeof("hello")
dostaniesz 6, a nie 5, ponieważ otrzymasz rozmiar tablicy, w tym terminator zerowy.
Kompiluje się czysto z gcc
Rzeczywiście, nawet ostrzeżenie. Wynika to z subtelnych szczegółów / błędów w języku C, które umożliwiają inicjowanie tablic znaków za pomocą literału łańcuchowego zawierającego dokładnie tyle znaków, ile jest miejsca w tablicy, a następnie po cichu odrzucają terminator zerowy (C17 6.7.9 / 15). Język celowo zachowuje się tak z powodów historycznych, zobacz Niespójna diagnostyka gcc do inicjalizacji łańcucha, aby uzyskać szczegółowe informacje. Zauważ też, że C ++ jest tutaj inny i nie pozwala na użycie tej sztuczki / błędu.
char str[] = "hello";
sprawie.char *str = "hello";
...str[0] = foo;
problem.sizeof
na jego użycie na parametrze funkcji, zwłaszcza gdy jest zdefiniowany jako tablica.Ze standardu C (7.1.1 Definicje terminów)
W tej deklaracji
literał łańcuchowy
"hello"
ma wewnętrzną reprezentację podobną dowięc ma 6 znaków, w tym końcowe zero. Jego elementy służą do inicjalizacji tablicy znaków,
str
która rezerwuje miejsce tylko dla 5 znaków.Standard C (w przeciwieństwie do standardu C ++) umożliwia taką inicjalizację tablicy znaków, gdy końcowe zero literału łańcucha nie jest używane jako inicjator.
Jednak w rezultacie tablica znaków
str
nie zawiera łańcucha.Jeśli chcesz, aby tablica zawierała ciąg, który możesz zapisać
Lub tylko
W ostatnim przypadku rozmiar tablicy znaków jest określany na podstawie liczby inicjatorów literału łańcuchowego równej 6.
źródło
Czy wszystkie ciągi znaków można uznać za tablicę znaków ( Tak ), czy wszystkie tablice znaków można uznać za ciągi znaków ( Nie ).
Dlaczego nie? i dlaczego to ma znaczenie?
Oprócz innych odpowiedzi wyjaśniających, że długość łańcucha nie jest nigdzie przechowywana jako część łańcucha, a także odniesienia do standardu, w którym łańcuch jest zdefiniowany, drugą stroną jest „Jak funkcje biblioteki C obsługują łańcuchy?”.
Chociaż tablica znaków może zawierać te same znaki, jest to po prostu tablica znaków, chyba że po ostatnim znaku następuje znak kończący wartość nul . Ten znak kończący wartość NUL pozwala na to, aby tablica znaków była traktowana (traktowana jako) ciąg znaków.
Wszystkie funkcje w C, które oczekują ciągu jako argumentu, oczekują, że sekwencja znaków zostanie zakończona na NUL . Dlaczego?
Ma to związek ze sposobem działania wszystkich funkcji łańcucha. Ponieważ długość nie jest uwzględniona jako część tablicy, funkcje łańcuchowe, przewijaj do przodu w tablicy, aż zostanie znaleziony znak nul (np.
'\0'
- równoważny dziesiętnemu0
). Patrz tabela i opis ASCII . Niezależnie od tego, czy używaszstrcpy
,strchr
,strcspn
itp .. Wszystkie funkcje łańcuchowe polegać na nul kończącego znaku są obecne w celu określenia, gdzie koniec tego łańcucha.Porównanie dwóch podobnych funkcji z
string.h
podkreśli znaczenie znaku kończącego wartość nul . Weź na przykład:strcpy
Funkcja po prostu kopiuje bajty odsrc
dodest
momentu nul kończącego znajduje charakter opowiadaniastrcpy
gdzie zatrzymać kopiowanie znaków. Teraz weź podobną funkcjęmemcpy
:Funkcja wykonuje podobną operację, ale nie bierze pod uwagę ani nie wymaga, aby
src
parametr był łańcuchem. Ponieważmemcpy
nie można po prostu skanować do przodu podczassrc
kopiowania bajtów do,dest
aż zostanie osiągnięty znak kończący wartość NUL , wymaga jawnej liczby bajtów do skopiowania jako trzeciego parametru. Ten trzeci parametr zapewniamemcpy
tę samą wielkość informacji, którąstrcpy
można uzyskać po prostu skanując do przodu, aż zostanie znaleziony znak kończący wartość nul .(co podkreśla również to, co idzie nie tak w
strcpy
(lub jakiejkolwiek funkcji oczekującej łańcucha), jeśli nie dostarczysz funkcji łańcucha zakończonego znakiem nul - nie ma pojęcia, gdzie się zatrzymać i z radością wybiegnie w pozostałą część twojego segmentu pamięci wywoływanie niezdefiniowanego zachowania, dopóki nul-postać nie znajdzie się gdzieś w pamięci - lub wystąpi błąd segmentacji)Czyli dlaczego funkcje spodziewa się znakiem NUL łańcuch musi być podjęło znakiem NUL łańcuch i dlaczego jest to ważne .
źródło
Intuicyjnie...
Pomyśl o tablicy jako o zmiennej (przechowuje rzeczy), a łańcuch o wartości (może być umieszczony w zmiennej).
Z pewnością nie są tym samym. W twoim przypadku zmienna jest zbyt mała, aby pomieścić łańcuch, więc łańcuch zostaje odcięty. („ciągi cytowane” w C mają na końcu niejawny znak zerowy).
Możliwe jest jednak przechowywanie łańcucha w tablicy, która jest znacznie większa niż łańcuch.
Pamiętaj, że zwykłe operatory przypisania i porównania (
=
==
<
itp.) Nie działają tak, jak można się spodziewać. Alestrxyz
rodzina funkcji jest bardzo bliska, gdy już wiesz, co robisz. Zobacz C FAQ na temat łańcuchów i tablic .źródło