Стартап Trasec разрабатывает новый язык программирования, который называется TrapC. Авторы проекта провозглашают его «наследником» C и C++. ЯП обещает устранить главные проблемы «предков», включая небезопасное управление памятью. Для этого в TrapC внедрены автоматические проверки и защита программ от типичных ошибок. Это делает невозможным выход за границы буфера или обращение к несуществующей памяти и значительно усложняет жизнь хакерам. Давайте оценим новинку.
В чем проблема C и C++
C и C++ — популярные языки программирования, «пионеры» отрасли, одна из основ современной ИТ-индустрии. Но у них есть проблемы. Они предоставляют разработчикам прямой доступ к управлению памятью, что, с одной стороны, обеспечивает высокую производительность и гибкость. С другой — эта же фича делает программы уязвимыми к ошибкам, связанным с памятью. Вот основные проблемы, ставшие поводом для критики:
переполнение буфера: классическая уязвимость, которую хакеры используют для выполнения вредоносного кода;
ошибки с указателями: некорректное использование ведет к обращению к несуществующей или освобожденной памяти;
утечки памяти: из-за ошибок в коде разработчики часто не высвобождают память, что приводит к росту потребления ресурсов;
сложность отладки: низкоуровневый доступ к памяти делает диагностику и исправление ошибок чрезвычайно трудоемкими.
Все это может привести к непредсказуемому поведению программ и создать лазейки для хакерских атак. Из-за этих проблем многие компании и отдельные разработчики начали искать альтернативы. Rust — один из главных претендентов, но переход на него связан с большими затратами времени и ресурсов.
TrapC появляется в качестве «философского камня», решающего все проблемы. Он предлагает концепцию «минимальной боли» благодаря своей совместимости с кодом упомянутых языков программирования.
TrapC разрабатывается стартапом Trasec, основанным Робином Роу и Габриэль Пантерой. Робин Роу — бывший профессор компьютерных наук, участвовавший в комитетах по развитию стандартов C и C++. Он также известен как соавтор графического редактора Cinepaint, использовавшегося при создании графики для ряда голливудских фильмов. Габриэль Пантера ранее занимала руководящую должность в компании Disney.
Они собрали команду из специалистов по языкам программирования и безопасности, чтобы создать новый хороший инструмент для разработчиков, отвечающий требованиям современного рынка.
TrapC: основные достоинства
У диалекта немало плюсов по сравнению с «обычными» языками программирования. Посмотрим, о чем нам рассказывают авторы проекта.
Революция в управлении памятью
Одно из центральных нововведений TrapC — переработанный подход к управлению указателями. Система обработчиков исключений (trap) следит за их безопасностью и предотвращает:
выход за границы буфера;
обращение к освобожденной памяти;
попытки некорректного преобразования типов.
Механизм интегрирован на уровне компилятора, что исключает необходимость в ручном управлении этими аспектами и существенно снижает вероятность ошибок. Вот пример кода. Заявлено, что TrapC не даст переполнить буфер buff при выполнении "strcpy(buff,argv[1]);" или не даст увеличить указатель или индекс массива на значение, смещающее его за пределы выделенного буфера или конца массива:
// darpa_tractor.c
int main(int argc,char* argv[])
{
char buff[8]; // TrapC implicitly zeros, no dirty memory
int success = 0;// In C, buffer overwrite corrupts success
strcpy(buff,argv[1]); // TrapC cannot overrun, strcpy safe
if(!strcmp(buff,"s3cr8tpw"))
{
success = 1;
}
if(success) // TrapC blocked strcpy overwrite, success good
{
printf("Welcome!\n");
}
return !success;
}
// trapc_ptr.c
int main()
{
const char* ptr = "Hello World"; // 12 char wide
while(ptr) // No buffer overrun with TrapC
{
printf("%c",*ptr); // print one char at a time
ptr++; // Steps off the end: TrapC nulls ptr!
} // Do NOT attempt this in C, will segfault!
assert(ptr == 0);
return 0;
}
// trapc_array.c
int score[10];
printf("%i",score[-1]); // TrapC will not allow access
for(int i = 0;i <= INT_MAX;i++) // C Undefined Behavior
{
printf("%i",i);
}
// In C, above code is an infinite loop, will not stop.
// TrapC blocks i++ overflow wrap-around, stops.
// TrapC will fail-safe, call error handler if defined.
Еще одна полезная фича — автоматическое управление памятью. В отличие от C и C++, где программист отвечает за выделение и освобождение памяти через вызовы malloc, free, new или delete, TrapC берет эту задачу на себя. Новый компилятор:
автоматически отслеживает, где память больше не используется;
высвобождает ее, предотвращая утечки;
анализирует код на этапе компиляции, чтобы указать на потенциальные проблемы.
Такой подход чем-то похож на принципы работы «сборщиков мусора», используемых в языках Java и Python. Но он сохраняет высокую производительность благодаря оптимизации работы компилятора.
Упрощение синтаксиса
TrapC устраняет избыточные ключевые слова и структуры. Например:
удалено слово
union
, которое в C и C++ использовалось для определения объединений, но часто приводило к ошибкам;исключены сложные конструкции для ручного управления памятью;
отсутствуют макросы: упрощение синтаксиса и устранение ошибок, связанных с их использованием;
простое преобразование типов: меньше ошибок, связанных с некорректным приведением типов.
«У TrapC меньше ключевых слов, чем у C, потому что я убрал union
и другие вещи. В первую очередь те, что я редко использую, и которые, как правило, вызывают проблемы. Да и в системах, критически важных для безопасности, вы их вряд ли будете использовать», — заявил Роу. По его словам, это делает TrapC более доступным для начинающих программистов, снижая порог вхождения, при этом опытные разработчики ценят гибкость и совместимость с привычными инструментами.
Безопасность без потери производительности
Многие опасаются, что добавление дополнительных проверок и автоматического управления памятью снизит производительность кода. Но разработчики TrapC уверяют, что их компилятор минимизирует накладные расходы, выполняя большинство проверок во время компиляции, а не работы программы. Это позволяет TrapC сохранять конкурентоспособность по скорости с C и C++, оставаясь более безопасным.
Будущее TrapC
Стартап Trasec планирует открыть исходный код компилятора TrapC в 2025 году — это станет важным шагом в плане популяризации языка. Open-source-код даст возможность сообществу разработчиков:
вносить улучшения в компилятор;
адаптировать язык под специфические задачи;
создавать собственные инструменты и библиотеки для работы с TrapC.
Открытость также повысит доверие к проекту, позволяя экспертам убедиться в его надежности и эффективности. Это может стать мощным стимулом для массового перехода на TrapC, особенно среди организаций, ищущих баланс между безопасностью, производительностью и совместимостью с существующими системами.
TrapC уже вызывает интерес у компаний, работающих с критически важным ПО, где каждая ошибка может стоить миллионов. Основные отрасли, где может пригодиться этот диалект:
ПО для авиации и медицины;
создание банковских и финансовых систем;
разработка ядра операционных систем.
Кстати, можно сравнить возможности новинки с Rust. Последний считается одной из самых популярных альтернатив C и C++, но TrapC предлагает иной подход. Если Rust полностью пересматривает концепцию работы с памятью, делая язык сложным для освоения, TrapC старается сохранить простоту и привычность C, убрав при этом его недостатки.
Эти два языка могут сосуществовать, предлагая разработчикам выбор в зависимости от их целей и навыков. У TrapC есть все шансы занять свою нишу среди языков программирования, активно используемых в критически важных областях. Но станет ли он популярным? Как говорят, поживем — увидим.
Комментарии (41)
Ydav359
04.12.2024 07:33Это же кошмар: меньше ключевых слов, чем в C, не понятно, завезли ли bool, строки и классы, еще и макросы убрали (а как же сишный #define true 1)
Gay_Lussak
04.12.2024 07:33Пока видно, что лучше некоторые моменты только по сравнению с С. Потому что аргументы против С++ просто смешны. Никто в здравом уме в продуктовом коде не будет использовать new, buf[], union и прочий С++89. Хочешь безопасно управлять ресурсами - вот тебе смарт поинтеры, хочешь контролировать выход за границы - вот тебе вектор с исключениями, хочешь структуру, хранящую разные типы - вот тебе variant. И внезапно все проблемы, описанные в статье исчезают.
eao197
04.12.2024 07:33Никто в здравом уме в продуктовом коде не будет использовать new, buf[], union и прочий С++89.
Как ни странно, но для union-а до сих пор есть место в продуктовом коде. Поскольку union применяют для экономии места и когда есть возможность определить тип содержимого union-а внешними средствами (или через общий префикс для содержащихся в union-е структур), то union-ом можно сэкономить, по меньшей мере, байт на каждый экземпляр union-а. А когда таких экземпляров пара-тройка миллионов, то и экономия получается немаленькая. Особенно с учетом эффекта выравнивания данных.
Abstraction
04.12.2024 07:33Но разработчики TrapC уверяют, что их компилятор минимизирует накладные расходы, выполняя большинство проверок во время компиляции, а не работы программы.
А это как, простите? В этих искусственных примерах - верю, но в них и любой линтер вроде cppcheck ругнётся. А если у меня в функцию передан указатель на начало массива и из совершенно другой переменной выводится его длина, то компилятор же хрен докажет что длина корректная, это нужно анализировать всю программу.
отсутствуют макросы: упрощение синтаксиса и устранение ошибок, связанных с их использованием;
Замена того что делалось макросами на примерно эквивалентные им шаблоны упростит синтаксис? Ну-ну.
Но (код) сохраняет высокую производительность благодаря оптимизации работы компилятора.
Какой-то словесный салат. Работающий эффективно компилятор и компилятор, порождающий эффективный код - это очевидно разные вещи. "Автоматически освобождает память" - программа/среда исполнения, а не компилятор. Если компилятор вставил в код проверку, а не надо ли "автоматически" освободить память - эта проверка съела минимум одну ассемблерную инструкцию, чудес не будет. (А в многопоточной программе такие проверки на произвольную память делать больно, многопоточные структуры стараются разнести во времени работу с данными и выделение памяти.)
serg_meus
04.12.2024 07:33Ada, Java, C#, D, Rust, Julia -- вот лишь верхушка длинного списка убийц языка C и примкнувшего к нему C++. Убивают-убивают, а воз и ныне там, некоторые убийцы сами канули в Лету.
Надо просто относиться к C как к мультиплатформенному языку ассемблера, чем он и был замыслен изначально. Есть в ассемблере неопреденное поведение, указатели и ручное управление памятью? Есть, и в этом часть его силы.
Как по мне, на замену C больше всего годен язык V, но он пока в бета версии.Vladislav_Dudnikov
04.12.2024 07:33В V же сборщик мусора есть, как он может быть убийцей Си в микроконтроллерах, например? Zig тогда уж лучший кандидат (ну или Rust).
serg_meus
04.12.2024 07:33Сборщик мусора отключается ключом компилятора -gc none
qw1
04.12.2024 07:33Но в языке нет new/malloc, соответственно нет и free. При выключении gc начинает течь память, или как удаляются аллоцированные объекты?
serg_meus
04.12.2024 07:33New и malloc нет. Память выделяется при декларирования (инициализации) объектов оператором
:=
или при добавлении их в коллекции. А вот free есть. У объекта можно вызвать метод free(), обернув вызов в блок unsafe{}, и тогда произойдёт его освобождение. Я проверял под Windows и Linux, память системе возвращается.qw1
04.12.2024 07:33Получается другой диалект языка. Если в V принято писать с gc, и все библиотеки так написаны, то просто добавление ключа компилятора всё сломает.
serg_meus
04.12.2024 07:33Не то, чтобы совсем сломает, но память вернется системе после закрытия программы. В языке V есть возможность пометить отдельные функции и методы как не использующие сборку мусора, что в принципе должно решить проблемы с производительностью, но я пока не пробовал.
qw1
04.12.2024 07:33unsafe убивает преимущества языка по безопасности, тогда уж можно писать на Си.
Можно, конечно, возразить, что критичные места пишем в unsafe, остальное в safe, но получается типичная java, где "всё критичное напишем на C++", а в итоге выходит, что GUI и простыни бизнес-логики написаны на языке с GC, и в целом система тормозит "размазанно", не получается найти одно то место, оптимизация которого даст 80% скорости.
serg_meus
04.12.2024 07:33Полностью согласен. Ждём, когда появится язык максимально быстрый, 100% безопасный, лаконичный, выразительный и без unsafe. Возможно, таковой появится, когда достаточно разовьётся ИИ.
Siemargl
04.12.2024 07:33Не совсем. Но под работу без gc придётся собрать свой стек библиотек.
В BetterC та же проблема.
Как модный вариант - бить на микросервисы, часть надёжные, часть как получится =)
ahabreader
04.12.2024 07:33Ada, Java, C#, D, Rust, Julia -- вот лишь верхушка длинного списка убийц языка C и примкнувшего к нему C++.
В этом списке только Rust целится именно на C и большое будущее ему гарантировано как минимум административными методами: движением правительства США за memory safety. Оно поставило вопрос ребром: не "приёмы безопасной работы с памятью", а "memory-safe языки", не "но в этом коде нет ручной работы с памятью", а "это код на memory-unsafe языке", не "декларация, как сделать мир лучше", а "пошевелитесь, иначе вы скоро заказ не получите". И какие остаются популярные языки кроме Rust в нише языков без сборки мусора?
Есть в ассемблере неопреденное поведение, указатели и ручное управление памятью? Есть, и в этом часть его силы.
Неопределённого поведения в нём всё-таки нет. Чтобы появилось, нужен некий оптимизирующий макроассемблер с тяжёлым прошлым - он должен появиться достаточно давно, чтобы ради оптимизаций оказалось допустимым молча выкидывать некорректный код.
geher
04.12.2024 07:33Неопределённого поведения в нём всё-таки нет. Чтобы появилось, нужен некий оптимизирующий макроассемблер с тяжёлым прошлым - он должен появиться достаточно давно, чтобы ради оптимизаций оказалось допустимым молча выкидывать некорректный код.
Так неопределенное поведение - это не само выкидывание кода, а только повод для него. А суть нетпределенного поведения в том, что предсказать это самое поведение программы невозможно. Например, если разименовать неинициализированный указатель, то можно попасть в нормальгый участок памяти и прочитать непонятно что, а можно вывалиться в недоступную память и вывалиться по попытке доступа к ней. Сишный компилятор замечает это UB и может выкинуть код исходя из соображения, что неопределенного птвпдения быть не может.
В простом ассемблере код выкидывать некому, но неопределенное поведение вполне можно словить, например, забыв закинуть в регистр в качестве адреса косвенной адресации кааое-то осмысленное значение. Содержимое регистра получается случайным (что осталось от предыдущих операций), а поведение программы становится неопределенным.
По моему как-то так
qw1
04.12.2024 07:33одержимое регистра получается случайным (что осталось от предыдущих операций), а поведение программы становится неопределенным
Я с этим не согласен. Поведение программы остаётся определённым, в регистре сохраняется результат от предыдущей операции.
Это примерно как в C++ выкинуть закомментировать строчку ниже и говорить, что появилось UB
int a = func1(); if (a == 0) return 0; // a = func2(); if (a == 0) return 0;
Нет тут UB.
serg_meus
04.12.2024 07:33Неопределённого поведения в нём всё-таки нет
Как это нет? А если я загружу в регистры и буду использовать неинициализированную ячейку памяти, то это что? Или коды введённых с клавиатуры символов буду сохранять в памяти программы, то это что?
kibb
04.12.2024 07:33Исполнятся те инструкции, коды которых записались в память.
Неициализированной памяти на уровне ISA (у обычных машин) не бывает, каждый байт содержит значение, которое является числом от 0 до 255.
Можно придумать теговую архитектуру, в которой у каждой ячейки будет дополнительный атрибут 'инициализировано', и которая будет трапаться при доступе к не инициализированной памяти (сигнальному значению). Но поведение все равно останется вполне определенным - сигнал о доступе к неинициализированной памяти.
qw1
04.12.2024 07:33Или коды введённых с клавиатуры символов буду сохранять в памяти программы, то это что?
И что, в программе ниже есть UB?
#include <conio.h> int main() { return kbhit(); }
serg_meus
04.12.2024 07:33Я имел в виду вот это:
#include <stdio.h> void main() { void *pMainAdr=(void *)&main; scanf("%s", pMainAdr); }
qw1
04.12.2024 07:33Если так, то загрузчик DLL и EXE - тоже UB? Потому что кто там знает, какой код лежит в загружаемых файлах...
serg_meus
04.12.2024 07:33Вызов произвольного кода это синоним неопределённого поведения. Если перед каждой загрузкой подменять DLL или EXE -- то это UB, иначе DB
code07734
04.12.2024 07:33Термин UB применяется когда спецификация языка позволяет более одной интерпретации UB-кода. То есть без знания версии и типа компилятора неясно какая именно программа получится на выходе. Даже если все из них не будут вызывать ошибок доступа к памяти и прочего непредсказуемого поведения суть неопределенности в том что неизвестна конечная программа
Если же вы на ассемблере напишете программу которая читает с произвольной памяти или вызывает произвольный код - неизвестно какой будет результат выполнения, но поведение программы задано в явном виде в точности как вы её написали
Проблема останова и UB разные вещи
kibb
04.12.2024 07:33В ассемблере как раз UB нет. ISA для машины полностью определяет допустимые результаты исполнения.
qw1
04.12.2024 07:33Строго говоря, в древних ассемблерах типа 6502, Z80 были неопределённые опкоды и регистры, которые не документированы у официального разработчика архитектуры (но были написаны десятки книжек типа "Секреты процессора XXX", и программисты могли их использовать), а производители клонов этих процессоров либо не реализовывали эти опкоды, либо реализовывали их по-другому. Также, часто не документировались все биты флагового регистра. Например, флаги переноса/полупереноса были строго документированы для ADD/SUB, но плавали в логических операциях (AND/OR/XOR) - где-то не менялись, где-то копировались из соответствующей позиции источника или результата.
code07734
04.12.2024 07:33Unspecified behavior
qw1
04.12.2024 07:33Нет, в рамках ISA это именно Undefined. А когда берём конкретный чип, от Zilog, или немецкий клон от VEB, поведение становится Unspecified.
Аналогично можно сказать, что в C++ нет Undefined: берём конкретную версию конкретного GCC и получаем строго детерминированную компиляцию, пусть и Unspecified.code07734
04.12.2024 07:33Термин UB означает то что я уже выше написал. Факт того что вам неизвестны инструкции какого-то процессора по какой-любо причине - не называют UB. Явление действительно похожее - в случае UB с конкретным компилятором неопределенность уходит. А тут с конкретной реализацией процессора. Но UB всё таки про компиляцию ЯВУ
orefkov
04.12.2024 07:33C++: "Многие ждали меня в лесу, возле выкопанной ямы, знал бы ты, как им теперь к лицу пиджачок деревянный"
yerm
04.12.2024 07:33Вроде C# в своё время претендовал на роль убийцы С++. Во всяком случае при завершении очередного проекта на С++ в середине нулевых мне предложили либо переходить в проект на С#, либо уволиться.
Devastator82
04.12.2024 07:33Скрытый текст
Все стартапы обещают нам полеты на Марс за пару баксов. Дождемся релиза и посмотрим в деле не на выдуманных искусственных примерах. Простите за гифку - не удержался)
9241304
04.12.2024 07:33Странные товарищи. Если они при том же синтаксис могут улучшить работу с памятью, почему не лезут в стандарт? Понятное дело, что маркетинговую статеечку запилить проще, но все же
Nemoumbra
04.12.2024 07:33Я смотрел видео-презентацию этого TrapC, там было больше информации...
Они обещали классы завезти, с конструкторами и деструкторами. Причём слово new всё-таки есть, и означает оно там как раз создание объекта. А вот delete нет.
Гарантии отсутствия сегфолтов, утечек памяти или обращений вне границ. Никаких "garbage in => garbage out".
Скорость - не медленнее C/C++...
...однако в TrapC нет UB!
GC работает "не как в Java".
Встроенный тип "decimal". А ещё все принтфо-образные и сканфо-образные функции libc умеют работать с "%j" - JSON. В примере они суют какую-то структуру типа Point в printf по указателю, и она выводится в виде JSON.
Язык "простой": в нем нет наследования (да и нужно нам энто ваше наследование), нет шаблонов (не жили хорошо, нечего и начинать), нет перегрузок функций (горит сарай, гори и хата).
Одним словом, убийца C++! /s
Noah1
04.12.2024 07:33Зачем очередная замена C? Уже есть Zig, Rust, Odin. 3 прекрасных языка, выбирай какой хочешь.
Какая-то необычная пошлая тенденция на новые "низкоуровневые" языки, видимо всех ну очень задолбал C/C++.
hardtop
Так ещё можно zig посмотреть. Он, правда молодой и меняется, зато компилирует Си сам из коробки.