Mimo że uwielbiam C i C ++, nie mogę powstrzymać się od podrapania po wyborze ciągów zakończonych znakiem zerowym:
- Łańcuchy z prefiksem długości (tj. Pascal) istniały przed C.
- Łańcuchy z prefiksem długości przyspieszają działanie kilku algorytmów, umożliwiając ciągłe wyszukiwanie długości.
- Łańcuchy z prefiksem długości utrudniają powodowanie błędów przepełnienia bufora.
- Nawet na maszynie 32-bitowej, jeśli zezwolisz, aby łańcuch był wielkości dostępnej pamięci, łańcuch z prefiksem długości jest tylko trzy bajty szerszy niż łańcuch zakończony zerem. Na maszynach 16-bitowych jest to jeden bajt. Na maszynach 64-bitowych 4 GB to rozsądny limit długości łańcucha, ale nawet jeśli chcesz go rozszerzyć do wielkości słowa maszynowego, maszyny 64-bitowe zwykle mają wystarczającą pamięć, co sprawia, że dodatkowe siedem bajtów jest argumentem zerowym. Wiem, że oryginalny standard C został napisany dla niesamowicie słabych maszyn (pod względem pamięci), ale argument wydajności nie sprzedaje mnie tutaj.
- Prawie każdy inny język (tj. Perl, Pascal, Python, Java, C # itp.) Używa ciągów z prefiksem długości. Te języki zwykle pokonują C w testach porównawczych w zakresie manipulacji ciągami, ponieważ są bardziej wydajne w przypadku ciągów.
- C ++ nieco to poprawiło w
std::basic_string
szablonie, ale tablice zwykłych znaków oczekujące ciągów zakończonych znakiem zerowym są nadal wszechobecne. Jest to również niedoskonałe, ponieważ wymaga alokacji sterty. - Ciągi zakończone znakiem NULL muszą zarezerwować znak (mianowicie NULL), który nie może istnieć w ciągu, a ciągi z prefiksem długości mogą zawierać osadzone wartości NULL.
Kilka z tych rzeczy wyszło na jaw niedawno niż C, więc sensowne byłoby, aby C nie wiedział o nich. Jednak kilka było wyraźnie na długo przed pojawieniem się C. Dlaczego ciągi zerowane mają być wybierane zamiast oczywiście prefiksu o większej długości?
EDYCJA : Ponieważ niektórzy pytali o fakty (i nie podobały mi się te, które już przedstawiłem) na temat powyższego punktu wydajności, wynikają one z kilku rzeczy:
- Łączenie przy użyciu łańcuchów zakończonych zerem wymaga złożoności czasowej O (n + m). Prefiksowanie długości często wymaga tylko O (m).
- Długość przy użyciu łańcuchów zakończonych znakiem zerowym wymaga złożoności czasowej O (n). Prefiks długości to O (1).
- Długość i konkat to zdecydowanie najczęstsze operacje na łańcuchach. Istnieje kilka przypadków, w których ciągi zakończone znakiem zerowym mogą być bardziej wydajne, ale występują one znacznie rzadziej.
Z poniższych odpowiedzi wynika, że niektóre przypadki, w których ciągi zakończone znakiem NULL są bardziej wydajne:
- Kiedy musisz odciąć początek łańcucha i przekazać go do jakiejś metody. Naprawdę nie możesz tego zrobić w stałym czasie z prefiksem długości, nawet jeśli możesz zniszczyć oryginalny ciąg, ponieważ prefiks długości prawdopodobnie musi być zgodny z regułami wyrównania.
- W niektórych przypadkach, gdy przeglądasz ciąg znaków po znaku, możesz zapisać rejestr procesora. Zauważ, że działa to tylko w przypadku, gdy nie przydzieliłeś dynamicznie ciągu (ponieważ wtedy musiałbyś go zwolnić, wymagając użycia tego rejestru procesora, który zapisałeś, aby utrzymać wskaźnik, który pierwotnie otrzymałeś od malloc i przyjaciół).
Żadne z powyższych nie jest tak powszechne jak długość i konkat.
W odpowiedziach poniżej znajduje się jeszcze jedno stwierdzenie:
- Musisz odciąć koniec sznurka
ale ten jest niepoprawny - to tyle samo czasu na łańcuchy zakończone znakiem null i łańcuchy z prefiksem długości. (Ciągi zakończone znakiem NULL po prostu przyklejają null tam, gdzie ma być nowy koniec, prefiksy długości po prostu odejmują od prefiksu.)