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?

Author: Pete Kirkham, 2009-08-10

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.

 16
Author: Daniel Earwicker,
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.

 29
Author: weiqure,
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.

 7
Author: sharptooth,
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.

 6
Author: caf,
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.

 5
Author: Nick Johnson,
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.

 5
Author: Jason Williams,
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*.
 4
Author: Jimmy,
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.

 1
Author: Dan McGrath,
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)

 1
Author: Adriaan,
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.

 0
Author: Kirill V. Lyadvinsky,
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