
19 июня Торвальдс влил merge, который вычистил из ядра Linux функцию strncpy. Шесть лет работы, 362 коммита, семьдесят человек. Ради одной функции из стандартной библиотеки C.
Звучит как анекдот про бюрократию. На деле это наглядный разбор того, почему «просто заменить небезопасную функцию» в C — совсем не «просто», даже когда принципиального спора о замене нет.
Почему её считают «безопасной»
Из-за буквы n. strcpy копирует до нуля и легко улетает за буфер, а strncpy(dst, src, n) берёт максимум n байт — выглядит как та же функция, но с ремнём безопасности. Её и в самом ядре нашли в сотнях мест; что уж говорить про прикладной код.
Вера ложная. man про strncpy пишет прямым текстом: функция создаёт «null-padded character sequence, not a string». Это не про строки вообще. Появилась она в Edition 7 AT&T Unix около 1979 года — под имена файлов в directory entries, где поле фиксированной ширины и могло содержать ноль, один или несколько хвостовых нулей. Такие fixed-width поля живы и сейчас, но это редкий частный случай — а strncpy сорок лет тащат в обычный код как «strcpy, который не стреляет в ногу».
Стреляет, просто тихо. Если strlen(src) >= n, то strncpy запишет ровно n байт и не поставит завершающий \0. Дальше любой strlen, strcmp или printk("%s", …) уходит читать за буфер: мусор, чужая память, в плохой день — утечка данных. А если наоборот, strlen(src) < n, остаток буфера до n забивается нулями: скопировали четыре байта в буфер на четыре килобайта — получите 4092 лишних записи на ровном месте. В документации ядра API так и назвали — ambiguous и fragile: по одному вызову не понять, чего хотел автор. Строку с нулём? Padding? Поле фиксированной ширины?
Где зарыты шесть лет
Замена — это не «найти и поменять на безопасный аналог». Безопасного аналога нет. Под разные намерения — свой набор: strscpy() для нормальной C-строки с \0, strscpy_pad() — то же с обнулением хвоста, strtomem() / strtomem_pad() для полей фиксированной ширины без терминатора, memcpy_and_pad() для bounded-копии из источника, который может быть не завершён нулём, memtostr() / memtostr_pad() для обратного случая, ну и обычный memcpy(), если это вообще не строка. Восемь функций там, где раньше была одна.
И sed -i 's/strncpy/strscpy/g' тут не работает в принципе, потому что верный ответ зависит от намерения автора:
/* имя в поле фиксированной ширины внутри on-disk структуры — \0 не нужен */ strncpy(de->name, name, sizeof(de->name)); // → strtomem_pad() /* а это уйдёт в printk("%s", buf) — без \0 здесь read-overflow */ strncpy(buf, src, sizeof(buf)); // → strscpy()
Один и тот же вызов, разный верный ответ. И так 362 раза: каждый надо открыть и прочитать глазами — это строка или бинарное поле, нужен ли ноль, нужен ли padding, завершён ли источник, известен ли размер приёмника на компиляции.
Вот почему тянулось с 2019-го. Не потому что был спор — затея шла под крылом Kernel Self-Protection Project Киса Кука, который выпиливает не отдельные баги, а целые классы уязвимостей. А потому что 362 раза требовалось ручное решение, и одной автоправкой его не выразить. Больше всех разгрёб Джастин Ститт — 211 коммитов из этих 362.
В вашей C/C++ базе strncpy скорее всего живёт прямо сейчас, и кто-то когда-то вписал её именно как «безопасный вариант». Это не баг в трекере — это код, который надо перечитать. И если соберётесь чистить, главная работа будет не в замене функции, а в том, чтобы понять, что в каждом буфере вообще лежит. Ядру на это хватило шести лет и семидесяти человек — на API, который десятилетиями выглядел как безопасная версия strcpy.
Комментарии (129)

Deosis
23.06.2026 07:49null-terminated string появились во времена, когда даже 2 байт на длину строки было многовато по памяти.

MinimumLaw
23.06.2026 07:49А олды помнят еще и $-терминированные строки. Как по мне вопрос не в размере, вопрос в однобайтовой ASCII кодировке. В те времена, когда она была достаточной этого хватало. Сегодня... Сегодня сложно. Местами давно надо заменить, но сказать проще чем сделать.

blind_oracle
23.06.2026 07:49Мне кажется кодировка тут ортогональна.
Различия просто в том как делать: length-value или null-terminated.
Да, когда памяти кот наплакал - хочется сэкономить. Но, как и с nil-ами, кроилово ведёт к попадалову. Вместо того, чтобы сделать заголовок из пары байт - получили вот это, с нулём в конце... и все проблемы с этим связанные.

CitizenOfDreams
23.06.2026 07:49Различия просто в том как делать: length-value или null-terminated.
Наверное, length-value... если мы уверены, что эта length будет подсчитываться правильно. Сколько, говорите, байтов занимает одна буква?
Или все-таки null-terminated... если мы уверены, что не забыли поставить этот null в нужном месте. Сколько, говорите, байтов занимает одна буква?

randomsimplenumber
23.06.2026 07:49С нулем прелесть в том, что некоторые функции гарантируют 0 в конце буфера, некоторые нет, некоторые забивают нулями буфер до конца. Походу проектировались они разными людьми.

unreal_undead2
23.06.2026 07:49Сколько, говорите, байтов занимает одна буква?
str* работают с последовательностями байтов, кодировки на следующем уровне абстракции.

blind_oracle
23.06.2026 07:49В Си никаких уникодов нет и не было, особенно когда эти null-terminated изобретались.
А сейчас да, до сих пор умудряются проблемы не этом получать. К примеру в Расте есть у
String(который хранит UTF-8) безобидный методtruncate(n), который работает не по кодовым точкам, а блин по байтам (чтобы быть O(1)) и если ему указать байт посреди кодовой точки - просто паникует.Хотя могли бы сделать вот что-нибудь такое и оно было бы тоже O(1):
pub fn truncate(s: &str, n: usize) -> &str { let n = s.len().min(n); let m = (0..=n) .rfind(|m| s.is_char_boundary(*m)) .unwrap_or_default(); &s[..m] }Резало бы по ближайшей кодовой точке.

Apoheliy
23.06.2026 07:49Подозреваю, что дело не только в паре байт для указания длины.
null-terminated строки позволяли делать разбор текста (например, когда его вычитали из файла в память) не тратясь на дополнительное копирование, прямо на живом буфере. Натыкал нулей вместо разделителей - и вот тебе отдельные строки (привет, strtok). В те далёкие времена (да и сейчас, в общем-то) такая экономия была существенной.
Если же строки будут с полем длины, то такой финт уже не провернёшь и придётся копировать-перекладывать много-много данных.
(По правильному, ) один из хороших вариантов: для строки хранить два указателя (типа, как структура): начало и символ-за-концом. Но что-то "не взлетает".

strvv
23.06.2026 07:49нет, скорее всего упрощение программирования - проверка не нуль одна из базовых функций АЛУ, поэтому есть везде. вот и реализуема. попробуй на другой символ-терминатор - заклюют и заплюют.

winorun
23.06.2026 07:49Если мы используем length-value мы имеем максимальный размер строки. null-terminated же позволяет иметь строки не ограниченной длинны.

unreal_undead2
23.06.2026 07:49Понятно, что правильный тип length - это size_t или подобное.

winorun
23.06.2026 07:49size_t на 128 битах будет 8 байт, на каждую строку. Даже если она 2 байта. И тут можно дойти до идеи коротких и длинных строк.

unreal_undead2
23.06.2026 07:49Про влияние оверхеда на хранение длины на выбор null-terminated vs length-based здесь в коментах уже больше десятка раз написали )
А так то для коротких строк в плюсах давно уже SSO придумали, оно и лишнюю аллокацию в куче убирает.

strvv
23.06.2026 07:49Я сейчас не вспомню, почти 30 лет назад, но имхо у DEC VAX/VMS штатным типом строки было 2 байта префикса (длина) и потом строка. И, скорее всего, в PDP11 осях также, оттуда скорее всего пошло. так что в принципе, не такой и большой оверхед. это просто проще программировать - проверка на 0 в АЛУ - базовая, и есть во всех ассемблерах.

unreal_undead2
23.06.2026 07:49C null-terminated можно дёшево работать с подстроками - для обработки хвоста можно просто передать указатель на первый символ посередине оригинала, в случаях посложнее можно временно занулить какой-нибудь символ. С явной длиной это подороже.

strvv
23.06.2026 07:49Не сильно. Если глядеть на весь оверхеэд с этими гигантскими библиотеками-фреймворками о всём и ничем, из которых используется пара функций, некоторые ранее просто замещались макросом в инклюде… То не сильно. В принципе в любом решении можно найти как плюсы так минусы. Так и выбор подстроки не до конца в твоём предложении дает неоднозначный эффект, а в случае с префиксом длины унифицированный вариант. Код проще и быстрее. Просто например в с++ вызывается конструктор, в него по принципу CoW передается указатель на начало строки, и заполняется префикс=если длина старой - позиция < длины подстроки ? Разница : длина подстроки. И всё

unreal_undead2
23.06.2026 07:49Это сейчас библиотеки-фреймворки, а когда то каждый нормальный программист считал байты и такты.

Wesha
23.06.2026 07:49И, скорее всего, в PDP11 осях также
Нет, не так. PDP-11 MACRO-11 Language Reference Manual , стр. 6-21, параграф 6.3.5

strvv
23.06.2026 07:49Спасибо, напомнили о теплых ламповых документах, с шрифтом под печатную машинку

xenon
23.06.2026 07:49А олды помнят еще и $-терминированные строки
"Я не вспоминал int 21h N дней."
Сбрасываю счетчик на ноль. Снова 20-30 лет его копить буду....

LeraKholod
23.06.2026 07:49Сейчас мы расплачиваемся за эту экономию бесконечными buffer overflow уязвимостями. Длина в начале строки решила бы кучу проблем с безопасностью

Wesha
23.06.2026 07:49Стек адресов возврата, отделённый от стека данных, решил бы кучу проблем с безопасностью...

unreal_undead2
23.06.2026 07:49Это вы про Эльбрус? ;)

Wesha
23.06.2026 07:49Это вы про Эльбрус? ;)
Нет, это я не про гору.
Это я чуть менее, чем про все эксплоиты, заточенные на переполнение локальных массивов или строк.
А с «Эльбрусом» не работал, увы.

unreal_undead2
23.06.2026 07:49Я "Эльбрус" живьём только в музее Яндекса грязными руками трогал, но другого железа с раздельными стеками физически вроде не видел.

Wesha
23.06.2026 07:49Мысль в том, что огромное количество эксплоитов заточено за то, чтобы переполнить какую-то строку или массив специально подобранными данными, чтобы она вылезли за пределы отведённого на них буфера на стеке и затерли ранее запихнутые туда адреса возврата — и когда придет время возвращаться из функции, управление пойдёт совсем не туда, откуда ее вызывали — а туда, куда надо взломщику.
Так вот, если временные переменные и адреса возврата будут на разных стеках, то такой финт ушами не проканает.

unreal_undead2
23.06.2026 07:49Это понятно, мысль не новая - но с реализациями в железе негусто. На iapx432 (1981 год) вроде тоже такое было - но это скорее экземпляр для кунсткамеры.

Wesha
23.06.2026 07:49А зачем обязательно в железе? «Стек данных» можно и программно двигать.

randomsimplenumber
23.06.2026 07:49Без аппаратного SP уныло будет

Wesha
23.06.2026 07:49А в чём проблема-то? Заводим ячейку, назваем её
SPd; обычно для выделения места под временные переменные добавляем требуемый объём кSP— а тут будем добавлять кSPd, всего-то. Да, будет работать чуть-чуть медленнее. Зато управление не будет передаваться куда не положено.

Wesha
23.06.2026 07:49Совершенно верно — но испорченные данные в гораздо меньшем количестве случаев могут привести к эффектам, к которым приводит переход по указанному адресу.

unreal_undead2
23.06.2026 07:49Да, будет работать чуть-чуть медленнее
В этом и проблема, и не факт что чуть чуть (если какая то функция вызывается каждую тысячу-другую тактов).

Wesha
23.06.2026 07:49если какая то функция вызывается каждую тысячу-другую тактов
...то у вас есть две проблемы.

strvv
23.06.2026 07:49Да, но надо слишком многое переделывать, в первую очередь компиляторы. На дековских можно было любой регистр использовать косвенный с автоинкрементом/декрементом, А здесь вместо push/pop городить конструкции

Wesha
23.06.2026 07:49Так пусть они определятся, что им важнее — ускорить выполнение программы на 0,5% или не дать китайским хакерам получить доступ к документам Пентагона.

beerchaser
23.06.2026 07:49Да ладно… Любой процессор Гарвардской архитектуры. Микроконтроллеры Pic например. Куда их только не засовывали.

unreal_undead2
23.06.2026 07:49Там код и данные разделяются, насчёт стеков не уверен - не писал под них.

Mox
23.06.2026 07:49Ходят легенды про язык тех времен, где в строке первый байт определял ее размер.
Я думаю что это реально неплохое решение по сравнению с null terminated, сразу точно знаешь сколько памяти выделено.
randomsimplenumber
23.06.2026 07:49256 байтов тогда всем хватало ;)
Зато любая операция со строками == неочевидная пляска с выделением памяти под капотом. Или как там оно внутри устроено?

unC0Rr
23.06.2026 07:49Тип строки имел фиксированный размер. Т.е. строка без указания максимального размера занимала 256 байт, с указанием размера - этот размер + 1. Преобразования между разными типами очевидны.

SpiderEkb
23.06.2026 07:49Это "паскалевские строки".
Последние 8 лет работаю с языком, где два типа строк - char и varchar.
char - обычный буфер заданной длины. И больше туда не впихнуть
dcl-s s1 char(5); dcl-s s2 char(10); s2 = '0123456789'; s1 = s2; // s1 = '01234' - что не влезло, то не влезло s2 = s1; // s2 = '01234 ' - остаток забивается пробеламиПри все желании за границу не вылезешь.
Но...
%len(s1); // всегда 5 %len(s2); // всегда 10а чтобы узнать реальную длину (без хвостовых пробелов) надо
%len(%trimr(s2));varchar - фактически структура
dcl-s vs varchar(10);в С будет представлено как
struct t_varchar { unsugned short len; char data[10]; } vs;и тут уже %len вернет реальное значение поля vs.len
И все это достаточно безопасно. Особенно с учетом того, что здесь не бывает неинициализированных переменных - компилятор автоматически инициализирует любую объявленную переменную дефолтным для ее типа значением (если явно не указано иное значение) - для char это "пустая" (заполненная пробелами) строка, для varchar это строка с len = 0, для числовых типов 0.

AndreyDmitriev
23.06.2026 07:49null-terminated string появились во времена, когда даже 2 байт на длину строки было многовато по памяти.
В общем да, хотя в 80-е был Паскаль и там уже были строки (хотя в оригинальном Паскале семидесятых их не было), а потом Ansi появились, в девяностых вроде, я как раз тогда Си учил после Паскаля, и сишные нуль-термиированные строки вызвали некоторое недоумение, было дело. Потом я на Дельфи работал. В общем дело привычки и аккуратности.

kunix
23.06.2026 07:49Че-то прошли мимо того факта, что strncpy работает с байтами, а не с UTF-8 и поэтому может разрезать символ посредине. Хотя да, по сравнению с отсутствием нуля это мелочи. Ужасная функция :)

denisgrigoriev04
23.06.2026 07:49Не думаю, что в 95% кода всего ядра нужно что-то кроме ascii

randomsimplenumber
23.06.2026 07:49Драйвер ФС может работать с utf8.

unreal_undead2
23.06.2026 07:49Кодировки для FS в ядре - это костыль для FAT и подобного, чтобы вместо CP866 показывать KO8-R. В нативных FS имена файлов - просто последовательности байтов, скажем никто не мешает использовать невалидную utf-8 последовательность.

glebliutsko
23.06.2026 07:49В linux драйвер ФС работает с байтами. Разрешены любые байты кроме
0x00и0x2F (Слеш /)Название файла может даже не быть корректной кодировкой. Все преобразования уже userspace происходят.


aulitin
23.06.2026 07:49Не может, байты продолжения в UTF8 всегда с 10хххххх начинаются

kunix
23.06.2026 07:49Ну ок.
А если буфер src закончился посреди символа?
Каким образом эта функция учтет UTF-8?
https://github.com/torvalds/linux/commit/079a028d6327e68cfa5d38b36123637b321c19a7#diff-caf1d936b395dcac087bd2b6d8585de0e06695cfe00c899d9299dc9cfec2a118L91char *strncpy(char *dest, const char *src, size_t count) { char *tmp = dest; while (count) { if ((*tmp = *src) != 0) src++; tmp++; count--; } return dest; }
aulitin
23.06.2026 07:49если закончится, то коррапченый символ в utf-8 это меньшая проблема :-)

kunix
23.06.2026 07:49Все же потенциальное отсутствие '\0' - явно задокументированная особенность и с ней умеют работать через конструкцию вида strncpy(buf, ..., sizeof(buf)-1).
Как здесь например:
https://github.com/torvalds/linux/commit/340ff3216799a947fe0b07bed8f0409ffc716be9#diff-81db6161fb0345ecabeac4f346089871ab4d62d9e8ee1fdb04c73757b8e8bbb8L133
А вот фигня с UTF-8 - более тонкая, неочевидная, и непредсказуемая.

nomorewar
23.06.2026 07:49Там же вроде ядро линукса на раст переписывают? Эта проблема сама бы не ушла, если использовать не С-строки?

Answer_M
23.06.2026 07:49Ядро не равно модули ядра. Да и модули как я помню не переписываются, а пишутся новые под новое железо или специфические кейсы

blind_oracle
23.06.2026 07:49Ничего там не переписывается. Добавили тулчейн чтобы можно было писать что-то в ядре на Расте да и всё.
Переписать всё ядро - понадобится, небось, миллион человеко-лет.

Revertis
23.06.2026 07:49Или один Claude Code и несколько месяцев.

blind_oracle
23.06.2026 07:49Угу, сжечь токенов но 100 миллионов долларов и получить в конце кусок говна :)
Тут одни уже Bun переписывали на Расте...

Revertis
23.06.2026 07:49Прочитал. Там одни предполодения, без каких-либо фактов.

blind_oracle
23.06.2026 07:49Факты? Факты будут когда покажут что это ИИ-поделие работает, его легко поддерживать и так далее. А не просто куча `unsafe {}` блоков повсюду.

LeraKholod
23.06.2026 07:49Проблема Си-строк никуда не уйдет, пока жив сишный ABI. Любой язык, который общается с ядром, вынужден подстраиваться под эти нули в конце

Mox
23.06.2026 07:49Ядро не будут переписывать на rust, только драйвера
Для тех кто хочет переписать на Rust есть https://github.com/asterinas/asterinas

Spiritschaser
23.06.2026 07:49то
strncpyзапишет ровноnбайт и не поставит завершающий\0. Дальше любой...уходит читать за буферО, спасибо, что экскурс в историю описали, как он появился. Всегда когда ещё писал на древнем до-ANSI С поражался этой разнице с strcpy. Даже был какой-то паттерн: использовать объявленные константы заданного размера и в них делать strncpy - дикое уродство, но типа безопаснее указателя с strcpy.

unreal_undead2
23.06.2026 07:49Занулить последний байт после вызова strncpy - это то, что пальцы сами набирают автоматом ) Вот от оверхеда на зануление хвоста на маленьких строках никуда не денешься.

lrlunin
23.06.2026 07:49Это не про строки вообще.
Замена — это не «найти и поменять на безопасный аналог».
Это не баг в трекере — это код, который надо перечитать.
Постить ИИ текст — это не про качественный постинг.

trinxery
23.06.2026 07:49Автора таки забанили в рид-онли, правда снесли только последнюю статью (под которой, в отличие от остальных, таки поднялось возмущение по поводу ИИшности).

randomsimplenumber
23.06.2026 07:49Зато он избавил нас от
цыганstrncpy ! (С придыханием) (ц) ;)Ну, функция стремная, да. Но она протестирована вдоль и поперек? Почему бы не оставить как есть и просто обьявить deprecated ? Она же и в glibc, и в куче программ продолжает торчать. 6 лет переписывали то что и так работает.

farafonoff
23.06.2026 07:49по нынешним временам - неизвестно работает или нет. Там могли быть скрытые неочевидные баги, если автор кода не до конца понимал все крайние случаи. Видимо решили, что функция - очевидный code smell, который нужно искоренять.

strvv
23.06.2026 07:49Согласен, сколько недавно, в апреле-мае вытащили багов с эскалацией привилегий через модули шифрования (algif?) С 17 года были.

LeraKholod
23.06.2026 07:49Deprecated не мешает людям копипастить старый код. Единственный способ избавиться от проблемы - физически удалить ее источник

LeraKholod
23.06.2026 07:49Проблема strncpy не только в терминаторе, она еще и кэш убивает своим забиванием нулями хвоста буфера, если строка короткая..

0xC0CAC01A
23.06.2026 07:49Зачем вообще кто-то придумал строки, заканчивающиеся нулём? Не лучше ли просто хранить длину строки? А ещё хорошо бы и размер буфера хранить. И тогда внезапно все эти переполнения буфера, донимающие нас уже пол-века, были бы не страшны. Не говоря уже про strlen(), выполняющийся за О(1).

slonopotamus
23.06.2026 07:49И что, теперь куча старого софта тупо перестала компилироваться?
Нет, функцию убрали из кода ядра. В libc она никуда не делась.

Belarus
23.06.2026 07:49В те времена память была дорого́й, экономили как могли. Видимо, создатели языка много работали с текстом (компиляцыя кода?), и там это удобно: што-то делаеш в любом месте текста и просто сохраняеш указатель на это место.

JBFW
23.06.2026 07:49С интересом наблюдаю за изобретающими Pascal-строки...
На тех граблях уже попрыгали, и перешли на нормальные null-terminated. Но нет, надо попрыгать снова, потому что кому-то лень думать о том, что делает функция, за него компилятор думать должен, и защищать
от мошенниковвыхода за пределы буфера.
GROMILOKBIG
23.06.2026 07:49Зачем тратить лишние когнитивные ресурсы и оставлять риск человеческого фактора?

JBFW
23.06.2026 07:49Затем, что если не думать над архитектурой (а даже несколько возможных вариантов решения какой-нибудь сортировки -тоже архитектура), и просто тупо лепить один на один кирпичики - получается большой, тормозной и глючный Франкенштейн, который развалится не от переполнения буфера, а из-за своей кривизны и непродуманности.

alexartrider
23.06.2026 07:49Автор не разобрался в теме, не понял почему и где баг - проблема этой функции не в том, что, есть в конце '\0' или нет, на это есть документация, а в UTF-8. Размер символа сегодня может быть 1,2,3,4 байта, и эта функция разрезает последний символ на части. А если не разрезать, а копировать полностью то получается переполнение буфера при копировании. Как то так выходит на самом деле.

artden111
23.06.2026 07:49То, что
strncpyможет не поставить завершающий\0гораздо хуже, чем если\0разрежет UTF8-символ. Хотя и в разрезании символа нету ничего хорошего
alexartrider
23.06.2026 07:49Читаем стандарт, она не должна ставить '\0'. Нет проблемы здесь, надо уметь читать мануалы.

kunix
23.06.2026 07:49Говно еще в том, что strncpy принятно использовать без проверки на ошибки.
Было переполнение или нет - коду пофиг, он молотит дальше.Когда-то были дыры из-за того, что например PHP видит строку "evil\0innocent" и она проходит проверку, а нативный код видит "evil\0".
Даже если отдельно порешать проблему с '\0', расширив буфер, проблема с неоднозначностью строк из-за обрезки остается.
Я даже подозреваю, что если хорошенько поигратьсясо шрифтамис UTF-8, можно из двух неудачно обрезанных последовательностей собрать UTF-8-франкенштейна с новыми символами.Короче, strncpy - непредсказуемое говно.

dude_figvam
23.06.2026 07:49дак на что поменяли?
strncpy_sнадеюсь?
DGG
23.06.2026 07:49На разное, в зависимости от контекста.
В статье написано, в частности, почему эту функцию нельзя было заменить на что-то одно.

zoiaylinsk
23.06.2026 07:49Зашли обсудить один коммит в ядре Linux, а в итоге судим Кернигана и Ритчи за решения 1972 года

SpiderEkb
23.06.2026 07:49Тут скорее камень в огород тех, кто никак не может закопать стюардессу. И продолжает на фундаменте 72-го года пытаться строить hi-tech небоскреб.
ch1971
да и вообще эти null-terminated string это вещь про которую можно сказать что если бы её никогда не существовало то всем на белом свете было бы легче жить)
Помню они меня так достали в C++ что я написал класс обёртку c операторами приведения во все разумные типы чтобы вообще никогда их не использовать... а потом пол года ещё дебажил сам этот класс)
wl2776
Дыкть, сам null назван его автором, Тони Хоаром, ошибкой на миллиард долларов.
tenzink
Там речь про nullable значения, а не про null-terminated строки
ch1971
А вот с этим как раз не соглашусь. NPE это сигнал что программа работает не так как должна. И чем раньше это выяснится тем лучше. А вот если NPE будет както замаскированно или автоисправлено или ещё чтото подобное и программа с ошибкой "на борту" похромает кудато дальше то кончиться всё это может хуже чем просто поиск ошибки в коде.
wl2776
Я тут смешал две больших проблемы (которые существовали, кажется, ещё до моего рождения:) ): как обозначить, где кончается строка, и как обозначить, что указатель никуда не указывает.
Для решения обеих чаще всего используется 0 (или null).
andreymal
Раньше — это во время компиляции. NPE, вывалившийся в рантайме на проде — это слишком поздно. (И ещё хорошо если в принципе вывалившийся, а не по-тихому портящий память...)
ch1971
Конечно на этапе компиляции это ещё лучше чем при выполнении. Но разговор то про то что сама концепция вредоносна. Вон например в Котлине есть возможность завести типы которые не могут быть null то есть вынуждают заполнять неинициализированные ссылки ссылками на какой нибудь dumb а потом какой нибудь модуль может решить что этот dumb вполне себе "живой" объект и начать с ним работать ну и пошло поехало...
кстати ну вот не припомню чтобы null поганил память если можно обрисуйте механику...
andreymal
У котлина null safety из коробки, в нём такой проблемы изначально нет. Ну а если кто-то принципиально игнорирует null safety и лепит dumb объекты — ну штош, защита от дурака не спасает от
долбслишком изобретательных дураков ¯\_(ツ)_/¯wl2776
Почему обязательно ссылки и обязательно на dumb? Деталей Вы не указали, но я бы к архитектуре вопросы поднял, мне такой подход странным кажется.
В каких-нибудь embedded адрес 0x00 вполне себе легальный адрес.
И вот еще, статья 10 летней давности на тему разыменования null.
upd. В той статье выводов мало, но вот есть еще: https://habr.com/ru/companies/contentai/articles/205070/
В целом, есть варианты, когда null пролезет и притворится валидным указателем.
ch1971
Спасибо за информацию. Никогда не знал что через null можно обратиться к методу. Как страшно жить)...
а про ссылки равные null ну например разреженный массив но чтобы не в виде коллекции с перегруженным [] а именно с индексным доступом.
legolegs
Не соглашусь. Это очень изящное и экономичное решение для множества задач (т.е. кроме текстовых редакторов). А ещё переход всего FOSS на UTF-8 не дался бы так легко с другими более сложными типами строк (см винду и трудности с её UTC32, _w -функциями в API и т.д.).