Dlaczego null-zakończone ciągi? Lub: null-zakończone vs. znaki + przechowywanie długości
Piszę interpreter języka w C, a mój typ string
zawiera atrybut length
, Jak Tak:
struct String
{
char* characters;
size_t length;
};
Z tego powodu, muszę spędzać dużo czasu w moim interpreterze obsługującym tego rodzaju ciągi znaków ręcznie, ponieważ C nie zawiera wbudowanej obsługi dla niego. Rozważałem przejście na proste ciągi zakończone znakiem null tylko po to, aby spełnić podstawowe C, ale wydaje się, że jest wiele powodów, aby nie: {]}
Bounds-sprawdzanie jest wbudowane, jeśli używasz "length" zamiast szukać dla null.
Musisz przejść cały łańcuch, aby znaleźć jego długość.
Musisz zrobić dodatkowe rzeczy, aby obsłużyć znak null w środku zakończonego null łańcucha.
Zakończone Null łańcuchy źle radzą sobie z Unicode.
Nie zakończone null łańcuchy mogą intern więcej, tzn. znaki "Hello, world" i "Hello" mogą być przechowywane w tym samym miejscu, tylko o różnej długości. Nie można tego zrobić z zakończonymi znakami null.
String slice (Uwaga: ciągi są niezmienne w moim języku). Oczywiście druga jest wolniejsza (i bardziej podatna na błędy: pomyśl o dodaniu sprawdzania błędów begin
i end
do obu funkcji).
struct String slice(struct String in, size_t begin, size_t end)
{
struct String out;
out.characters = in.characters + begin;
out.length = end - begin;
return out;
}
char* slice(char* in, size_t begin, size_t end)
{
char* out = malloc(end - begin + 1);
for(int i = 0; i < end - begin; i++)
out[i] = in[i + begin];
out[end - begin] = '\0';
return out;
}
Po tym wszystkim, moje myślenie nie jest już o tym, czy powinienem używać łańcuchów zakończonych null: myślę o tym, dlaczego C ich używa!
Moje pytanie brzmi: czy są jakieś korzyści z wypowiedzenia zerowego, które mi umknęły?
10 answers
Zwyczajowym rozwiązaniem jest zrobienie obu rzeczy-zachowanie długości i utrzymanie terminatora null. To nie jest dużo dodatkowej pracy i oznacza, że zawsze jesteś gotowy, aby przekazać ciąg do dowolnej funkcji.
Zakończone Null ciągi są często drenażem wydajności, z oczywistego powodu, że czas potrzebny na odkrycie długości zależy od długości. Z drugiej strony, są one standardowym sposobem reprezentowania ciągów w C, więc nie masz wyboru, ale je wspierać, jeśli chcesz użyć większości C biblioteki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:12:14
From Back To Basics :
Dlaczego ciągi C działają w ten sposób? Dzieje się tak dlatego, że mikroprocesor PDP-7, na którym wynaleziono UNIX i język programowania C, miał Typ asciz string. ASCIZ oznaczało " ASCII z (zero) na końcu."
Czy to jedyny sposób na przechowywanie sznurków? Nie, to jeden z najgorszych sposobów na przechowywanie sznurków. W przypadku nietrywialnych programów, API, systemów operacyjnych, bibliotek klas, należy unikać ciągów ASCIZ, takich jak zaraza.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:15:08
Jedną z korzyści jest to, że z zakończeniem null każdy ogon zakończonego łańcucha null jest również zakończonym łańcuchem null. Jeśli chcesz przekazać ciąg znaków zaczynający się od N-tego znaku (pod warunkiem, że nie ma przekroczenia bufora) do jakiejś funkcji obsługi łańcuchów - nie ma problemu, po prostu przekaż tam adres offseeted. Podczas przechowywania size w inny sposób trzeba by skonstruować nowy ciąg znaków.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:09:01
Jedną z zalet łańcuchów zakończonych znakiem nul jest to, że jeśli przechodzisz przez łańcuch znak po znaku, musisz tylko zachować pojedynczy wskaźnik, aby adresować łańcuch:
while (*s)
{
*s = toupper(*s);
s++;
}
Podczas gdy dla ciągów bez Sentinel, musisz zachować dwa bity stanu wokół: wskaźnik i indeks:
while (i < s.length)
{
s.data[i] = toupper(s.data[i]);
i++;
}
...lub aktualny wskaźnik i ograniczenie:
s_end = s + length;
while (s < s_end)
{
*s = toupper(*s);
s++;
}
Kiedy rejestry CPU były rzadkim zasobem (a Kompilatory gorzej je alokowały), było to ważne. Teraz nie bardzo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:45:34
Nieco offtopic, ale jest bardziej efektywny sposób, aby zrobić Ciągi z prefiksem długości niż sposób, w jaki opisujesz. Utwórz taką strukturę (poprawną w C99 i w górę):
struct String
{
size_t length;
char characters[0];
}
Tworzy to strukturę o długości na początku, z elementem' characters ' używanym jako znak*, tak jak w przypadku bieżącej struktury. Różnica polega jednak na tym, że można przydzielić tylko jeden element na stercie dla każdego ciągu, zamiast dwóch. Przydziel swoje ciągi TAK:
mystr = malloc(sizeof(String) + strlen(cstring))
Eg - długość struktury (która jest tylko size_t) plus wystarczająco dużo miejsca, aby umieścić za nią łańcuch.
Jeśli nie chcesz używać C99, możesz to zrobić również za pomocą "znaków znakowych [1]" i odjąć 1 od długości łańcucha do przydzielenia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-20 20:13:46
Długości też mają swoje problemy.
Długość wymaga dodatkowego przechowywania (nie taki problem teraz, ale duży czynnik 30 lat temu).
Za każdym razem, gdy zmieniasz ciąg znaków, musisz zaktualizować długość, aby uzyskać zmniejszoną wydajność.
Z zakończonym znakiem NUL możesz nadal używać długości lub przechowywać wskaźnik do ostatniego znaku, więc jeśli robisz wiele manipulacji ciągiem, nadal możesz równać wydajność sznurek-z-długością.
Ciągi zakończone znakiem NUL są znacznie prostsze - terminator NUL jest tylko konwencją używaną przez metody takie jak
strcat
do określenia końca łańcucha. Możesz więc przechowywać je w zwykłej tablicy znaków, zamiast używać struktury.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-23 13:41:57
Po prostu wyrzucam jakieś hipotezy:
- nie ma sposobu, aby uzyskać " złą " implementację zakończonych znakiem null łańcuchów. Ustandaryzowana struktura może jednak mieć implementacje specyficzne dla dostawcy.
- nie są wymagane żadne struktury. Null zakończone ciągi są" wbudowane", że tak powiem, ponieważ są szczególnym przypadkiem znaku*.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:14:38
Chociaż preferuję metodę array + len w większości przypadków, istnieją ważne powody, dla których można użyć null-terminated.
Weźmy 32-bitowy system.
Aby zapisać 7 bajtowy łańcuch
char * + size_t + 8 bajtów = 19 bajtów
Aby zapisać 7-bajtowy łańcuch null-term
char * + 8 = 16 bajtów.
Tablice Null-term nie muszą być niezmienne, tak jak twoje łańcuchy. Mogę szczęśliwie obciąć ciąg c, umieszczając po prostu znak null. Jeśli kodujesz, musisz utworzyć nowy ciąg znaków, co wiąże się z alokacją pamięci.
W zależności od użycia strun, twoje struny nigdy nie będą w stanie dopasować możliwej wydajności z ciągami c w przeciwieństwie do twoich strun.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:14:04
Masz absolutną rację, że 0-terminacja jest metodą, która jest słaba w odniesieniu do sprawdzania typu i wydajności dla części operacji. Odpowiedzi na tej stronie podsumowujÄ ... Juĺľ jej pochodzenie i zastosowania.
Podobał mi się sposób, w jaki Delphi przechowuje ciągi. Wydaje mi się, że utrzymuje długość / maxlength przed łańcuchem (zmienna długość). W ten sposób łańcuchy mogą być zakończone znakiem null dla zgodności.
Moje obawy związane z Twoim mechanizmem: - dodatkowy wskaźnik - niezmienność si w podstawowych częściach Twojego języka; Zwykle typy ciągów nie są niezmienne, więc jeśli kiedykolwiek zastanowisz się nad tym, będzie to trudne. Trzeba by zaimplementować mechanizm "create copy on change" - korzystanie z malloc (mało wydajny, ale może być tu zawarte tylko dla ułatwienia?)
Powodzenia; pisanie własnego tłumacza może być bardzo pouczające w zrozumieniu głównie gramatyki i składni języków programowania! (przynajmniej dla mnie ws)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 09:53:19
Myślę, że głównym powodem jest to, że standard nie mówi nic konkretnego o rozmiarze jakiegokolwiek typu innego niż char. Ale sizeof (char) = 1 i to zdecydowanie za mało dla rozmiaru łańcucha.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-10 06:07:18