Кіріспе

Деректер құрылымы

Компьютерлік бағдарламалауда, нөлмен аяқталатын жол – бұл таңбалардан тұратын массив ретінде сақталатын және нөлдік таңбамен (осы мақалада "NUL" деп аталатын, ішкі мәні нөлге тең таңба, нөлдік символмен шатастырылмау керек) аяқталатын таңба тізбегі. Балама атаулары – C жолы, C бағдарламалау тіліне сілтеме жасайды және ASCIIZ (бірақ C ASCII-ден басқа кодировкаларды да қолдана алады). Жолдың ұзындығы (бірінші) NUL іздеу арқылы анықталады. Бұл жол ұзындығына байланысты O(n) (сызықтық уақыт) уақытын қажет ететіндіктен, баяу болуы мүмкін. Сондай-ақ, жолдың ішінде NUL таңбасы болмайды (жадта NUL бар, бірақ ол соңғы таңбадан кейін орналасқан, жолдың өзінде емес).

Тарих

Нөлдік аяқталатын тізбектер PDP 11 құрастыру тілдерінің ASCIZ директивасымен және PDP 10 үшін MACRO 10 макро құрастыру тілінің ASCIZ директивасымен жасалды. Бұл C бағдарламалау тілінің дамуына дейін пайда болған, бірақ басқа да тізбектер жиі қолданылды. C (және одан туындаған тілдер) дамытылған кезде жад өте шектеулі болғандықтан, тізбектің ұзындығын сақтау үшін тек бір байтты пайдалану тартымды болды. Сол кездегі жалғыз танымал балама, көбінесе "Паскаль тізбегі" деп аталатын (қазіргі заманғы термині "ұзындығы префикстелген"), тізбектің ұзындығын сақтау үшін алдыңғы байтты қолданды. Бұл тізбекте NUL символын пайдалануға мүмкіндік берді және ұзындығын табу үшін тек бір жадқа қол жеткізу қажет болды (O(1) (тұрақты) уақыт), бірақ тізбектің ұзындығы 255 символмен шектелді. C тілінің авторы Деннис Ричи, тізбектің ұзындығына шектеу қоймау және оның тәжірибесінде санауды сақтау, терминаторды пайдаланудан гөрі ыңғайсыз болғандықтан, нөлдік аяқталу әдісін таңдады. Бұл процессордың нұсқаулар жиынтығының дизайнына әсер етті. 1970 және 1980 жылдары Zilog Z80 және DEC VAX сияқты кейбір процессорларда ұзындығы префикстелген тізбектерді өңдеуге арналған арнайы нұсқаулар болды. Дегенмен, нөлдік аяқталатын тізбектер кеңінен таралған сайын, CPU дизайнерлері оны ескеруді бастады, мысалы, IBM-нің 1992 жылы ES/9000 520-ға "Логикалық тізбекке көмек" нұсқауларын және 2015 жылы IBM z13-ке векторлық тізбек нұсқауларын қосу шешімінде көрінеді. FreeBSD әзірлеушісі Poul Henning Kamp, ACM Queue журналында жазғанда, 2 байттық (бір байт емес) ұзындықты жеңген нөлдік аяқталатын тізбектерді "ең қымбат бір байттық қате" деп атады.

Шектеулер

Бұл бейнелеуді іске асыру оңай болғанымен, ол қателерге және өнімділік мәселелеріне бейім. Нөлдік соқтыру тарихи түрде қауіпсіздік проблемаларын тудырды. Сызықтың ортасына енгізілген NUL оны күтпеген жерде тоқтатады. Көбінесе кездесетін қате – NUL үшін қосымша орынды бөлуді ұмыту, нәтижесінде ол жақын жадқа жазылып, бұзады. Тағы бір мәселе – NUL-ді жазуды мүлдем жіберіп алу, бұл сынақ кезінде көрінбейтін, себебі жад блогында бұрыннан нөлдер болған. Ұзындығын анықтаудың қиындығынан көптеген бағдарламалар, сызықты белгілі өлшемдегі буферге көшіргенде, оның ұзындығын тексермейтін, егер сызық тым ұзын болса, буфердің толып кетуіне себеп болатын. Нөлді сақтау мүмкін болмағандықтан, мәтіндік және екілік деректерді бөлек сақтау және оларды әртүрлі функциялармен өңдеу қажет (екіншісі деректердің ұзындығын да талап етеді). Бұл кодтың қайталануына және дұрыс емес функция қолданылғанда қателерге әкелуі мүмкін. Ұзындықты анықтау жылдамдығының мәселесін strlcpy сияқты O(n) болатын басқа операциямен біріктіру арқылы шешуге болады. Дегенмен, бұл әрқашан түсінікті API-ге әкелмейді.

Таңба кодтамалары

Нөлдік аяқталатын тізбектер кодтаудың кез келген жерінде нөлдік байтты (0x00) пайдаланбауын қажет етеді; сондықтан барлық мүмкін ASCII немесе UTF-8 тізбектерін сақтау мүмкін емес. Дегенмен, ASCII немесе UTF-8 тізбектерінің NUL таңбасынан басқа барлық таңбаларын нөлдік аяқталатын тізбектерде сақтау жиі кездеседі. Кейбір жүйелер "өзгертілген UTF-8" қолданады, ол NUL-ді екі нөлдік емес байт ретінде (0xC0, 0x80) кодтайды, осылайша барлық мүмкін тізбектерді сақтауға мүмкіндік береді. Бұл UTF-8 стандартында рұқсат етілмейді, себебі бұл артық ұзын кодтау болып есептеледі және қауіпсіздік қаупін тудырады. Тізбектің соңы ретінде UTF-8-де қолданылмайтын 0xFE немесе 0xFF сияқты басқа байттарды да пайдалануға болады. UTF-16 2 байттық бүтін сандарды қолданады және кез келген байты нөл болуы мүмкін (әсіресе ASCII мәтінін көрсету кезінде әрбір екінші байт нөл болады), сондықтан оны нөлдік аяқталатын байт тізбегінде сақтауға болмайды. Алайда, кейбір бағдарламалау тілдері 16 биттік UTF-16 таңбалар тізбегін, 16 биттік NUL (0x0000) арқылы аяқталатын күйде жүзеге асырады.

Жағдайдың жақсаруы

C тізбектерін басқаруды қателерге осалдығын азайту үшін көптеген әрекеттер жасалды. Бір стратегия – strdup және strlcpy сияқты қауіпсіз функцияларды қосу, ал gets сияқты қауіпсіз емес функцияларды қолданудан бас тарту. Тағы бір тәсіл – C тізбектерін объектіге бағытталған қаптамаға орау, осылайша тек қауіпсіз шақырулар жасалуын қамтамасыз ету. Дегенмен, қауіпсіз емес функцияларды шақыруға мүмкіндік бар. Көптеген қазіргі заманғы кітапханалар C тізбектерін 32 бит немесе одан да үлкен ұзындық мәнін қамтитын құрылыммен алмастырады (бұл ұзындығы алдынан қосылатын тізбектер үшін ескерілгеннен әлдеқайда көп), сондай-ақ көбінесе тағы бір көрсеткіш, сілтеме саны және тіпті NUL символын қосып, C тізбегіне қайта түрлендіруді жеделдетуге мүмкіндік береді. Жады қазір әлдеқайда үлкен, сондықтан егер әр тізбекке 3 (немесе 16 немесе одан да көп) байт қосу нақты проблема тудырса, бағдарламалық құрал көптеген кішкентай тізбектермен жұмыс істеуі керек болады, сондықтан басқа сақтау әдісі одан да көп жадты үнемдеуі мүмкін (мысалы, көптеген дубликаттар болған жағдайда хэш-кесте жадты тиімдірек пайдалануы мүмкін). Мысалдарға C++ стандартты үлгілер кітапханасы std::string, Qt QString, MFC CString, Core Foundation-нан C-ге негізделген CFString және оның Objective C әріптесі Foundation-нан NSString, екеуі де Apple компаниясына тиесілі. Сондай-ақ, жіптерді сақтау үшін арқан сияқты күрделі құрылымдар қолданылуы мүмкін.