Часу обмаль: Б’ярн Страуструп наполягає на негайних змінах для збереження C++

Б’ярн Страуструп, творець C++, закликав комітет WG21 терміново вжити заходів для збереження актуальності мови на тлі зростаючого інтересу до безпечніших мов програмування, які мінімізують помилки у роботі з пам’яттю.

На його думку, дедлайн близько: до 2026 року Агентство з кібербезпеки та ФБР рекомендують розробникам перейти на безпечні мови або впровадити механізми захисту від помилок у роботі з пам’яттю.

Щоб забезпечити конкурентоспроможність C++, Страуструп пропонує стандартизовану систему профілів, яка дозволить писати безпечний код без необхідності переходу на інші мови. Вона працюватиме подібно до компіляторних флагів -Wall та -Wextra, але вводитиме суворіші обмеження на рівні синтаксису, забороняючи небезпечні конструкції.

Запропоновані профілі охоплюють безпечні типи, контроль часу життя об’єктів, перевірку діапазонів значень, роботу з цілочисельною арифметикою та механізми запобігання станам гонки. Їх можна застосовувати на рівні проєкту, файлів чи окремих конструкцій.

Реалізація цих змін дозволить поетапно підвищувати безпеку коду, уникаючи використання сирих покажчиків, неконтрольованого приведення типів і звернень до неініціалізованих об’єктів. Наприклад, сирі покажчики можна замінити на std::unique_ptr та std::shared_ptr, а традиційні C-масиви та for-цикли — на std::vector із безпечним перебором елементів.

Серед запропонованих профілів:

  • type — кожен об’єкт має бути ініціалізований, приведення типів заборонене.
  • lifetime — заборонені посилання на звільнену або невикористану пам’ять, розіменування покажчиків, явний виклик new/delete.
  • bounds — обов’язкова перевірка допустимих діапазонів при роботі з покажчиками, заборонені арифметичні операції з покажчиками.
  • arithmetic — блокуються цілочисельні переповнення, заборонені знакові/беззнакові перетворення, що змінюють значення.
  • concurrency — виключає операції, які можуть призвести до взаємних блокувань і станів гонки.
  • RAII — вимагає контролю володіння кожним ресурсом.

Це забезпечує такі гарантії, як відповідність звернення до об’єкта його визначеному типу, коректне створення та звільнення кожного об’єкта, вказування покажчика лише на коректний об’єкт або нульовий покажчик. Крім того, жодне посилання через покажчик не може здійснюватися через нульовий покажчик, а кожне звернення через індексований покажчик має залишатися в межах допустимого діапазону.

Як Ви вважаєте: це рішення допоможе C++ утриматися на плаву, чи його час уже сплив, і корабель приречений піти на дно?

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному0
LinkedIn

Найкращі коментарі пропустити

Допоки на C++ мільярди строк коду — на дно він не піде.

Але гарно, якщо додадуть зміни, щоб зробити код безпечнішим. Головне, щоб це було опціонально — для забезпечення плавного переходу.

Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Найголовніша проблема С++, як не дивно, не сама мова, а код, написаний за 30 років на плюсах. Користувачі С++ розділені на два табора: діди та молоді. Перших багато, а коду, написаного ними для бізнесу ще більше і якісь великі зміни у мові унеможливлюються в таких обставинах. Молоді вимагають змін, діди стають на заваді цього, ось і тупік.

Є приклад з xorg коли new generation чи не змогла, чи не захотіла то розробляти/змінювати у вигляді як то бачать насьогодні — і створила купу проектів на базі wayland (включно з ним самим). Тобто існування legacy і мова якою пишуть то явно не головна причина щось змінювати (чи не змінювати).

Трохи додам опцій a-vs-b:

наскільки я зрозумів Майкрософт переносить Typescript (toolset) і зробила вибір на користь Go замість Rust, аргументуючи тим що раст не задовольняє багатьом їх потребам порівнюючи з go. І доволі суперечливі повідомлення відносно в скільки Nx раз виросте при цьому перфоманс.

Це до того, що С++ взагалі не розглядався?

Сам процес вибору цікавий (які критерії і чому саме вони) для транзиту конкретного крупного проекта.
Щось типу с++ не розглядався за відсутністю gc, с# бо класи не треба, rust бо cycling data, і т.д.

Ну... C# скоріше за усе не розглядався з маркетингових причин, все ж таки ставити .NET може бути занадто для більшості користувачів, хочеться простіше. Rust... ну ніби-то є проекти, коли розробка заходила у архітектурний глухий кут. Знову ж таке, на початку хочеться написати транслятор, щоб було щось робоче, а транслювати щось на Rust... треба переписувати повністю під концепцію.

Тому напевне Go виглядає найбільш привабливо.

В різних анонсах і інтерв’ю вже розписали чому, с# саме за класів (певно непотрібний оверхед за рахунок функціоналу класів який їм не треба). Відносно раст що побачив що borrow-checker не сподобався (не зовсім зрозуміло чому) і робота з cycling data struct, і частково (наскільки я зрозумів) що треба кодерів притягнути а з раст то буде важче. Сумарно по всім критеріям — обрали конкретний варіант.

Хоча могли просто анонсувати результат вибору і нічого не пояснювати — робимо бо треба бути в категорії safe2026, бо всі так роблять, і т.д. — та обрати умовний pascal.
А якщо тех.деталі процесу вибору розказали — по ним можна і проїхатися.

с# саме за класів (певно непотрібний оверхед за рахунок функціоналу класів який їм не треба)

Я не вірю людям на слово. В принципі, пиши усе на статіках й не буде тобі класів. Тому це в принципі мені не зрозуміло, як і великий оверхід А от озвучувати що не кожен захоче ставити у себе dotnet... Не думаю, що це збігається з головної лінією Microsoft. Тому треба було придумати якесь пояснення, ок, оверхід від класів.

Відносно раст що побачив що borrow-checker не сподобався (не зовсім зрозуміло чому)

Тому що транслятор не напишеш. Ну і... не факт, що була впевненість, що вони переможуть Rust, а не він їх.

А що значить вони переможуть раст? вони і є одним із його промоутерів.

Ще був критерій — присутність на більшості таргет платформ/систем — невпевнений чи с# вписався би в такому випадку.

А що значить вони переможуть раст?

Я краєм вуха чув про один проект кодека, який починався на Rust, був швидше за сішний, але в часом він став однопоточним, та більш повільним. Тобто може виникнути ситуація, коли з розвитком складності проекту, обмеження, які накладає сама мова Rust, стають настільки сильними, що їх задовільнити без шкоди для фіч та швидкодії стає дуже й дуже складно.

присутність на більшості таргет платформ/систем

А це знову таки той факт, что dotnet дуже великий.

чув про один проект кодека, який починався на Rust

не знаю, мені аргументація нп з cycling data виглядає більш конкретно (а чому не borrow-checker — не дуже зрозумілою)

dotnet дуже великий

і тому (за рахунок що дуже великий dotnet) с# розглядають як умовно нішевий? (присутність не на всіх платформах)

с# розглядають як умовно нішевий?

Точніше навіть корпоративний. Великі застосунки розробляти великий сенс. Щось лайтове наврядчи

З першим (крупні проги) окау, а друге — в цьому контексті це typescript — його відносимо до лайтових проектів?

Ну ти пишеш якийсь аналог pygmentize. Якщо я зроблю apt install pygments і мені запропонують поставити dotnet (або доставити, якщо моя версія застаріла), то.... заради однієї утіліти я не буду це робити.

це ж не в кінцевому продукті? чи з усим, включно рантайм, потім буде притягуватись?
А так згоден, має сенс в аргументації

чи з усим включно з рантайм потім буде притягуватись?

Якщо у нас є Console.WriteLine то нам вже треба System.dll. А де його брати? Можна зробити так:

dotnet publish -r linux-x64 --self-contained true -p:PublishTrimmed=true

але тоді в нас ELF може бути 50-100 Mb

Взагалі з рантайм якісь варіанти придумати певно були. Якщо він відносно невеликий — мож щось типу як з dart/flutter бінарниками, якщо дуже великий — тоді дійсно — уппс бувби.

ELF може бути 50-100 Mb

:) думаю що ще не так довго часу до того як мейнстрім браузери в розряд гігабайтних бінарників перейдуть

-rwxr-xr-x  1 root root  3355968 Oct 10 08:07 test-arm-aot
root@efpsnode-89-7f0574df:~# time ./test-arm-aot
Running on .NET 9.0.0
CPU:  ARMv7 Processor rev 5 (v7l)

real    0m0.032s
user    0m0.009s
sys     0m0.021s

ніяких 50-100 Мб. швидкий старт навіть на малопотужному залізі.

звісно що з додаванням функціональності розмір бінаря виросте. але шоб до 50МБ — то надо дууууууже постаратись

Бо вони переносили з максимальних збереженням структури існуюючого Тайпскрипт коду. І так, Раст для цього не дуже підходив.

От саме reasoning для «не дуже підходив» доволі цікавий (як для раст так і для інших паскалів)

Якщо я правильно зрозумів, то дуже грубо це можна сформулювати як «не підходив для напівавтоматичного переносу 1 в 1»

Ну тобто вони не «створили новий компілятор тайпскрипта на нативній мові» а «транслювали існуючий на нативну мову». Ну і власне нативна мова мала дозволити цю трансляцію

Щодо переносу 1 в 1 я не бачив, що структурно і власне в плані заюзаних парадигм ближче до go — то так і є. Мене зацікавила нп аргументація «чому ні» відносно borrow-checker (rust) і класів (с#).

Відносно C# — я так зрозумів, що в компіляторі ТС використовуються функції, які треба було б загортати у класи у випадку С#.

Під Rust треба було б перепроектовувати усі структури компілятора і переписувати весь код, скоріш за все.

Так, відносно с# зрозуміло. Відносно rust приблизно таку саму (як наведену) загальну аргументацію побачив там — занадто дофіга переробляти треба булоб, кодерів проблемно заманити буде, і т.п.
Але ще й з точки зору функціоналу самої мови трохи пройшлись по borrow-checker, саме ця частина аргументації не зовсім ясна.

Ще раз, припустимо ми пишемо транслятор TypeScript на Go і на Rust. У нас є код

function update(obj: { value: number }) {
    let ref1 = obj;
    let ref2 = obj;
    ref1.value += 1;
    ref2.value += 2;
}

На Go його можна перекласти досить просто:

func update(obj *Obj) {
    ref1 := obj
    ref2 := obj
    ref1.value += 1
    ref2.value += 2
}

А на Rust?

fn update(obj: &mut Obj) {
    let ref1 = obj;
    let ref2 = obj; // Borrow checker !!!
    ref1.value += 1;
    ref2.value += 2;
}

сенс цього раз-два-прикладу? якщо як раз borrow-checker для того щоб так не робили.
В аргументації були cycling data — що більш reasonable в плані обмеження borrow-checker (з cycling зрозуміло) — але як один з пунктів «проти», інші (які могли бути?) не озвучені, чи я пропустив десь.

Сенс прикладу у тому, що написати транслятор з TS на Rust неможливо (набагато складніше). Треба переосмилити.

якщо як раз borrow-checker для того щоб так не робили.

То код вже написали на TS, як нам перевести цей код на Rust?

Неможливо то занадто категорично, важче (чи значно важче) ніж з іншими варіантами — так. А те що це не трансляція 1в1 було нп відносно що робити з enum в go, тобто без суттєвих змін якихось частин не обійтися.

як нам перевести цей код на Rust

той «нам» повз мене точно, цікава тільки аргументація

що переосмислити? кривий дезігн? кривий

А на Rust?

А нащо так робити в першу чергу? Раст прямо забороняє те, чого і в інших мовах робити не бажано.
А так, Cell / RefCell / Rc / Arc в залежності від контексту.

І я може щось пропустив, але той компілятор наче руками писали, а не через транслятор.

Вони робили _трансляцію_ з Тайпскрипта. Вони _не переписували_ компілятор з нуля. У них була задача — грубо кажучи, виправити синтаксис, залишивши алгоритми та структури даних без змін (або з мінімальними змінами). Ні Раст, ні Сішарп, ні плюси для цього не підходили

Точно після цього obj.value збільшиться на три?

писав вище: «переосмислити дезігн»

можна почати звідси

fn update(obj: &mut Obj)

що тут відбувається?

func update(obj *Obj)

це точно мало би обновитися значення на яке вказує вказівники, точно це ідіоматік вей Rust чи в Go?

Як на мене виглядає «буду писати на любій мові програмування як на Паскалі»

Хто буде переосмислювати? ChatGPT? Ми хочемо написати автоматичний транслятор.

збільшиться на три?

ех.. ніякої емпатії, ніззя так просто привід для драми відбирати

по вказівнику, в русті, як в С, на три, кожному

Нехай агентство з кібербезпеки та ФБР займаються своїми безпосередніми обов’язками

я вам авторитетно говорю — с++ должен умереть или быть передан одной компании для владения. В теперешнем виде с++ это убитое гавно из 60х годов прошлого века. Эти вечные префиксы типа std::str — это все делает разработку на нем супер медленной. Отсуствие модульность превращает билды в часовое ожидание. Противостояние майкрософта у которого есть свой с шарп и гугла у которого дофига хороших идей и также убитых хороших идей через год после внедрения, делает развитие с++ невозможным.

Противостояние майкрософта у которого есть свой с шарп и гугла у которого дофига хороших идей и также убитых хороших идей через год после внедрения, делает развитие с++ невозможным.

на городі бузина а в Кийові дядька

Якщо у шановного пана автора статті попрохати пряме посилання на пряму мову пана Страуструпа про

дедлайн близько: до 2026 року

, то шановний пан автор статті цього не зможуть зробити, бо це є вигадкою.

C++ — це чисто опенсорсний проект, де ентузіасти власним коштом створюють для самих себе і своїх організацій мультипарадигмову мову програмування, оптимізовану для написання великих і складних проєктів, для яких performance і reliability є ключовими вимогами.

Починаючи із стандарта C++11, за рахунок введення концепції розумних вказівників, C++ надав необхідний і достатній інструментарій для написання memory safety кода.

Починаючи із стандарта C++11, було відпрацьовано механізм оновлення стандарту C++ кожні три роки, чим було започатковано еру конкурентоспроможної еволюції та процвітання C++.

Що зокрема проявилось у написанні на C++ всіх AI бібліотек, де performance matter.

Скоріш усього для клікбейтності записані разом дата дедлайн і стандарт С++26, з PR3611R0:
To address serious concerns about safety from important users of C++, we must do something urgently. That is, we need to include something significant in C++26.

А взагалі думаю що поштовхи ще були навіть до того, коли Мозіла підтримала одного з своїх розробників з Раст, як кардинальну опцію пофіксити С++ мембаги в Файрфокс (ідею «як» — потім підхвачену іншими біг компані) в свому громадному проекті з дуже широким діапазоном по якості коду і кваліфікацією тих хто приймає в тому участь. Але більш збалансований і більш широкий розгляд проблем пов’язаних з safety (не тільки memory) від Страуструп — виглядає краще. Проблема аж зовсім не нова, напевно просто прийшов час знаходити рішення як в рамках «unsafe» мов, так і пошук опцій альтернатив мов для автоматизації в крупних проектах.

Firefox і досі написаний переважно на JavaScript та С++. Mozilla foundation — банально усе виходить на ринок засобів розробки, створюючи конкуренцію на ринку в першу чергу Microsoft. Рекомендації щодо «кращої мови» постійно видають теоретики із Кернегі Мелоун які історично працюють як із Netscape так і Пентагоном.
Те що і С і С++ мають чисто теоретичні як недоліки такий переваги, частина з яких запозичена ще із про предка Algol 60 усім давно відомо. Просто Rust одна з перших реально зроблених компіляторів, де вдалось повністю реалізувати оригінальну ідею Едгара Дейкстери — контроль за часом життя об’єктів в HIP на етапі компіляції/збірки. Що було занадто складно і не було відповідних обчислювальних ресурсів в 1960-му році, щоби це реалізувати в компіляторі.
Технічно нічого не заважає ввести такі самі можливості в нові стандарти С++ і компілятори із С++. Та є питання зворотної сумісності, це по перше. А по друге — на мові з такими фітчами вже не одне буде писати в ручну апаратно оптимізований код, виключно доведеться лікувати фрагменти написані на С або ассемблері, як це відбувається в Rust. А це насправді великий, дуже великий недолік. Це і збільшує трудомісткість розробки в рази.

Firefox і досі написаний переважно на JavaScript та С++.

це не протирічить тому яку ідею спробували реалізувати і продовжують шукати рішення (в тому числі і від Раст опції не відмовились)

С і С++ мають чисто теоретичні як недоліки такий переваги

якби вони тільки теоретичні були, то ніхто би і не заморочувався. Як раз недоліки (як і переваги) — які існують на практиці в першу чергу.
За теорію (як і за документацією доречі) згадують зазвичай в останню чергу, як кодери так і манагери.

А по друге — на мові з такими фітчами вже не одне буде писати в ручну апаратно оптимізований код

цю сентенцію важко розібрати, в плані зв’язку фічі та оптимізованого коду

Ну скажімо фітча із small string optimization зав’язана на апаратні трюки із union, ручною поправкою бітів та reinterpret_cast. Це усе в мовах типу : Java, С#, Python, Go lang, Rust або D і т.д. — можна роботи тільки на рівні самих компіляторів, віртуальних машин і т.д. але не безпосередньо в програмному коді.
В С++ робиться умовна компіляція із препроцесором де пишеться код під архітектури із Little Endian та Big Endian і усе працюватиме. Та з точки зору чистої теорії — це буде повне безчинство та теоретичний undefined behaviour, тоді як програміст знає, що це працюватиме саме в таких от програмно апаратних умовах і при чому дозволить вижати із обладнання максимальні можливості як по пам’яті так і по швидкості.
І так дуже багато де. Так С та С++ це інструменти гострі як лезо, з одного боку дають можливості зробити як шедевр так і кроваве місиво.

small string optimization

не впевнений що це дійсно актуальна задача в рамках загального розгляду safety

Та з точки зору чистої теорії — це буде повне безчинство та теоретичний undefined behaviour, тоді як програміст знає, що це працюватиме саме в таких от програмно апаратних умовах і при чому дозволить вижати із обладнання максимальні можливості як по пам’яті так і по швидкості.

Так і є, в рамках крупних проектів від цього стараються рухатися в напрямку більш deterministic коду, хоч би тому щоб легше все то було підтримувати (не думаємо ж що то роблять з думкою про зручності коду як input для ШІ, чи є така думка?)

Так С та С++ це інструменти гострі як лезо, з одного боку дають можливості зробити як шедевр так і кроваве місиво.

я сприймаю це як гасло на плакаті, тобто значною мірою перебільшення, бо на практиці відповідно гарні рішення (як і жахливі) для конкретної задачі можна отримати як в сі так і в інших мовах

Практика показала, що мають сенс обидва підходи. Тому в нас є і максимально можливо програмно апаратно незалежні скріптові мови як то : Python, JavaScript, Basic/Visual Basic, Perl, Lisp та подібні і т.п. Проміжні варіанти із різними ступенями програмно апаратної незалежності як то Java, C#, Oberon, Scala, Groovy, Kotlin і т.п. Новітні проміжні рівні теж із різними ступенями лімітацій як то : D, Go lang, Rust так і Modern C++ 11 та вище. Так же є канонічні : С, Pascal, Zic і взагалі ассемблер, який нікуди не подівся та звісно використовується лише в певних випадках.
Нічого на 100 універсального, щоби зробити те чого увесь час прагне бізнес чи державні замовники — максимально скоротити час розробки, при цьому отримувати найвищу можливу якість як по швидко дії так і по пам’яті, так і по новітнім можливостям — не винайдено. Зараз от Єрік Шмідт загально об’явив в Стенфорді — що це буде AI, який типу нарешті дозволить робити IT бізнес без залучення непотрібної гілки інженерів як таких. Та тут може і навпаки статисть — інженери зможуть замінити AI-єм цілу гору нахлібників типу Єріка Шмідта, та робити ІТ бізнес без пройдисвітів торговців.

Та тут може і навпаки статисть — інженери зможуть замінити AI-єм цілу гору нахлібників типу Єріка Шмідта

не думаю так, заміну менегерам вищої ланки ще не придумали, тай навіщо самім собі заміну шукати. Крім того в любому випадку візіонери, як нп Стів Джобс, потрібні щоб щонебудь якісно нове з’являлось.
А от з таксистами і програмістами складніше питання, по перше — це соціальний фактор за рахунок масовості. Як би то не дивно було, думаю що найкращі шанси в айті це у дизайнерів і дизайнерок, того у кого більш креативної складової в роботі, а програмастів які кодують calculus a+b легше замінити (перебільшую офкос, але напрямок думки думаю зрозумілий)

Та ну як саказати. Білл Гейтц, Стів Джобс чи навіть Ілон Маск, це як не крути хакери які пішли та започаткували бізнеси. І взагалі уся ця індустрія започаткована саме науковцями ті інженерами які пішли в бізнес, та перші як то через потреби інвестицій або брак знань із бізнесу та торгівлі : Джон Еккерт, та Джон Моклі пішли до класичного бізнесу, Remington Rand які займались виготовленням механічних арифмометрів, за капіталами.
IBM — теж одна із визначальних компанії індустрії, так само була компанією яка виготовляла друкарські машинки що стала виготовляти і комп’ютери, поглинаючи інші компанії та на комп’ютери був державний та бізнес попит тому вони не закривали поглинених — а навпаки продовжили цей бізнес.
Одними з перших інженерів в ІТ яким як і Томасу Едісону свого часу вдалось в бізнес, це Роберт Нойс та Гордон Мур, що створили Intel.
Чисті бізнесмени — наприклад Стів Балмер,або Джон Скаллі там закономірно усе погано.

Думаю трохи забагато персоналій для однієї вузької теми, і здається що Маск то з іншої опери в аспекті візіонерства. Краще повернутись до language safety і AI, а то ще трохи і вийде обговорення друкарських машинок в розрізі ведення бізнесу на Марсі.

Посилання на джерело є в публікації. Додатково надам посилання на фрагмент тексту про 2026 рік з першоджерела: www.theregister.com/...​/C++ community to respond.

Це не пряма цитата пана Страуструпа, а позиція CISA щодо поступової відмови від C/C++.

Я думав, що концепція

розумних вказівників

з нами десь з 1998-го року, просто в стандарт їх додали в 2011. А що справді створило

необхідний і достатній інструментарій для написання memory safety кода

це move semantic, нові smart pointers то вже похідне. Тепер кожен фреймворк як Qt не пише свої QString з copy on write

Move semantics було запроваджено у C++11 для отримання, підтримуваної на рівні стандарту мови програмування, можливості уникнення непотрібних копіювань, зокрема при RAII.

Розумні вказівники у C++ потрібні для отримання, підтримуваної на рівні стандарту мови програмування, можливості запобігання dangling pointers та memory leaks при роботі з динамічною пам’яттю.

Майже всі системи реального часу (real-time systems) написані на C, C++ та Ada. Rust не використовується через відсутність необхідних бібліотек, а також тому, що Rust досить молодий. Можливо, у майбутньому їх переведуть на Rust.

Думаю, C++ залишиться актуальним ще на десятиліття. Зараз C++ використовується в Boston Dynamics, Tesla, авіаціi. Також завдяки зростанню популярності штучного інтелекту C++ займає перше місце в трендах мов у Китаї.

Те, що США хоче перевести використання Rust замість C++, безумовно, є правильним рішенням для деяких систем. Це в деякій мірі спростить та удосконалить підхід до написання програм, але слід розуміти, що процес переходу буде не швидким.

Ще один цікавий момент: ті системи, в яких використовувався AI для написання коду на Rust, виявилися складнішими для розуміння людиною, ніж код, написаний AI на C++.

ті системи, в яких використовувався AI для написання коду на Rust, виявилися складнішими для розуміння людиною

якщо розглядати код в майбутньому як АІ input, то з такої точки зору — розуміння того коду людиною (як і взагалі читабельність) — не дуже важливі. Важливо щоб код був більш deterministic з точки зору AI, тому мабуть rust краще в то вписується.

А якщо розглядати іншим чином, то дуже важливо щоб код був зрозумілий, і шо?

Нічого, теж опція, в іншому аспекті можна отримати інші висновки

в яких використовувався AI для написання коду на Rust, виявилися складнішими для розуміння людиною, ніж код, написаний AI на C++.

...., для людей, які пишуть на С++ і не пишуть на Rust.

В последний раз я использовал С++ в коммерческой разработке наверное в 2016 году, на уровне перевести с 32 бита на 64 бита и пару багов за фиксить. Много работал до 2008 года, ну вот сотни тысяч строк кода прям. Сейчас же ситуация такая, я в прошлом году написал модуль на С для Питона. Считаю что С это вот то что надо, а С++ мертвый, для меня по крайней мере.

Хоча мені значно зручніше працювати з С кодом, треба віддати належне С++ софту який використовується практично кожним, починаючи як з мейнстрім браузерів так і купою крупних проектів як kde llvm etc, це навіть нічого не кажучи про ігрову індустрію.

Як Ви вважаєте: це рішення допоможе C++ утриматися на плаву

Щоб почати утримуватися на плаву, треба спочатку почати тонути.
Тобто логічніше булоб дискутувати точку зору (від name it) з початковим твердженням — тоне c++ чи ні, тоне java чи ні, тоне rust чи ні, тоне xxx чи ні ...

— відносно закликів Страуструп: нп колись закликав до більш calm approach до змін «We are on the path to something that could destroy C++. We must get off that path»

— з іншої сторони (memory safety hardware based on Capability Hardware Enhanced RISC Instructions, CHERI) висловлюють скептицизм взагалі відносно memory safety в усіх існуючих мовах програмування (включно з java rust etc)

— а з останнього що в інтернетах можна знайти це — the new administration has removed everything from the White House web site and fired most of the CISA people who worked on memory safety — чи вірити тому чи ні

Тобто наскільки і куди той memory safety needle схилиться — невідомо.

Вважаю, що і без цього рішення плюси займатимуть свою нішу і все у них буде нормально ще дуже й дуже довго, хто б їх скільки не хоронив. Але, безперечно, рухатися в напрямку підвищення безпечності мови також не завадить.

Як на мене, нових проектів не С++ не дуже й багато, бо велика конкуренція з іншими мовами. Рефакторити старий код... Не знаю, не впевнений...

Як замінити for цикл на std::vector у загальному випадку мені не відомо. А саме цікаве, це як досягти виключення операцій, які можуть призвести до взаємних блокувань і станів гонки. Щось на кшталт TLA+?

Як на мене, нових проектів не С++ не дуже й багато

Вистачає, це одна із най росповсюджений мов на планеті. Принаймні GamDev повно нових проектів саме на С++.
Таке враження через Україну, яка через бульбашку аутсорсу і простотою пошуку проектів має не такі тренди як глобальне IT.
В нас Fronted та JavaScript/TypeScript номер один, бо тут простіше за усе знайти якусь підрядну роботу в Internet хоча і маржа не дуже, бо не великий поріг входу і велика пропозиція. Так в цілому завжди було, LAMP PHP стек, зараз ще Frontend / Node стек — це де шукається відносно легкодоступна підрядна робота.
З Rust в нас так само усе погано, тільки якась крипта і т.п.

Принаймні GamDev повно нових проектів саме на С++

Ну... відносно нових проектів... Як дивитися :-) Зазвичай вони використовують Unreal Engine, який також доведеться переписати. Що ніхто робити не буде, бо навіщо там безпека? Людство змирилося з тим, що ігри забаговані та вилітають. Тому я не думаю, що там це якесь актуальне питання у GameDev.

А от окрім GameDev де? А ще без використання великих фреймворків, які, скоріше за усе, ніхто не буде переписувати під ці профілі?

Зазвичай вони використовують Unreal Engine

У нас Unity переважає. А по світу усе підряд 70% ринку в сектрорі «різне». Більшість відомих рушії які ліцензуються — С++ : Unreal, Id Tech, CryEngine.
Цей ринок мало аутсорсять, трохи по іншому працює є студії які агрегують великі видавці типу Activision-Blizard, EA, Ubisoft і т.д. (коли я працював був окремий відділок шхуни яка займалась усім по троху, най прибутковіший і най завантаженіший і прибутковий тоді був web відділок який робив Frontend та PHP. GameDev у тих горе бізнесменів, як потім з’ясувалось, був не рентабельним). Аутсорс в GameDev погано катується, як і усе складне в цілому. В тому числі і С++ і Rust так само.

Ще раз, перше питання чи є нагальна потреба у профілях безпеки у C++ для GameDev? Зазвичай швидкість розробки набагато важливіше. Питання друге, чи можливо запровадити профілі якщо більшість коду на проєкті це рушій ще й з особливими патчами? Скоріше за усе ні. Тому, як на мене, нові ігрові проєкти у GameDev не потребують профілі безпеки. Це навіть якщо прийняти, що це нові проєкти, тут теж є питання, можна розглядати як форки ігрових рушіїв.

як досягти виключення операцій, які можуть призвести до взаємних блокувань і станів гонки

никак, ровно как и в большинстве «безопасных» языков типа Java

Ну... якщо пофантазувати, то треба просто додати рівні для м’ютексів, та заборонити використання вищого рівня з функцій нижчого, наприклад, через атрибути.

все уже придумано в Rust

Як я зрозумів, deadlock не на рівні мови, а на рівні можливості потенційно те реалізувати? Ну... до речі в С++ це ніби-то можна:

#include <iostream>
#include <mutex>

template <int Level>
class HierarchicalMutex {
    std::mutex mtx;

public:
    static constexpr int level = Level;

    void lock() {
        mtx.lock();
    }

    void unlock() {
        mtx.unlock();
    }
};

template <int CurrentLevel, int LockLevel>
void lockMutex(HierarchicalMutex<LockLevel>& mutex) {
    static_assert(LockLevel > CurrentLevel, "Mutex locked in the wrong order! Must lock a strictly higher level.");
    mutex.lock();
}

HierarchicalMutex<1> low_mutex;
HierarchicalMutex<2> mid_mutex;
HierarchicalMutex<3> high_mutex;

template <int MutexLevel>
void safe_function() {
    lockMutex<MutexLevel>(mid_mutex);
    lockMutex<2>(high_mutex);
}

template <int MutexLevel>
void unsafe_function() {
    lockMutex<MutexLevel>(low_mutex);  // ERROR: Trying to lock level 1 after a higher level
}

int main() {
    safe_function<1>();
    // Uncomment to raise an error
    // unsafe_function<2>();

    std::cout << "Program executed successfully.\n";
    return 0;
}

Відстрелити собі ногу існує не так мало способів, в кожній мові програмування свої. В цілому же це спіймає будь який санітайзер, який має бути в проекті. В цілому же через RAAI усі користуються об’єктами типу std::lock_guard і такі проблеми дуже рідкі.
От де справді костиль це де і перформанс має бути, конкретно std::atomic та std::memory_traits.
По факту стандартизували бібліотеку boost::atomic, яка від початку була реалізована через вбудований ассемблер, тобто work around коли компілятори ще не підтримували на той час тоді новітні багато ядерні процесори. Через це з’явилась std::memory_traits — що по суті каже що модель пам’яті не в мові — а самому процесорі. Та як завжди в наявності низького рівня, є як недоліки так і переваги. Інструмент тонкий як бритва, що дозволяє робити як шедевр так і кроваве місиво, в залежності від того в чиїх він руках.

і такі проблеми дуже рідкі

Але призводять до дуже поганих наслідків та їх дуже важко знаходити та репродьюсити. Навіть якщо шанс зустріти за секунду проблему один на мільйон, то це означає що вона буде виникати один раз за 11 днів.

В цілому же це спіймає будь який санітайзер, який має бути в проекті.

Дійсно спіймає такий deadlock на мьютексах?

#include <iostream>
#include <mutex>
#include <thread>

std::mutex m1, m2;

void thread1() {
    std::cout << "Start Thread 1" << std::endl;
    std::lock_guard<std::mutex> lock1(m1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock2(m2);
    std::cout << "Thread 1 acquired both locks" << std::endl;
}

void thread2() {
    std::cout << "Start Thread 2" << std::endl;
    std::lock_guard<std::mutex> lock2(m2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock1(m1);
    std::cout << "Thread 2 acquired both locks" << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);

    t1.join();
    t2.join();

    return 0;
}

Valgrind/Hellgrind — звісно не на компайл таймі. Intel VTune і т.д. Взагалі Valgind ловить мало не усе, утічки пам’яті, race conditions — що куди як частіше трапляється ніж дуже рідкісний в дійсності dead lock
І Rust тут особливо не вірізняється
medium.com/...​nt-deadlocks-9ae6e4123037
Інша справа що std::sync::mpsc:: — це бомбезна штука. Тоді як уся бібліотека std::thread — то таке собі. Як я вже писав — саме в цій робочій группі, 7 років не можуть прийянти стандарт на пулл потоків.

Так то норм.
Що там завжди про c++ пишуть — він повинен бути найближче до hardware,або найбільш нізкорівневою МП.

Насправді вже в С++ є речі, які просто не можна використати при створенні системного коду. typeid, dynamic_cast — треба RTTI — несумісний із системним кодом. Thread local, exeptions і та статичні перемінні і т.д. — це усе чого не можна використовувати в системному коді.

Єдине не зрозумів чому статичні змінні не модна використовувати — цеж просто «синтаксичний цукор» для глобальних змін на рівні ОО мови.

Томущо за стандартом, ініціалізація має бути потоко-безпековою тому в ABI є мьютекс який захоплюється.

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

Власне на цьому і базується Singleton Маєрса.

сlass singleton
{
public:
    static singleton* instance() {
        static singleton inst;
        return &inst;
    }
private:
    singleton() {}
};
Відповідно — мьютекс сам по собі це системний виклик ядра операційної системи, відповідно тількино зробили локальну статичну перемінну — це одразу залежність на стандартну білбліотеку.
В з GCC/G++ можна дати ключ -fno-threadsafe-statics і пройде, краще в коді драйвера наприклад нічого такого не робити взагалі. Ніяких сінглтонів і тому подібного, не кажучи вже про код ядра хоча там не всюди С++ взагалі треба, С і то вже може бути занадто і навпаки код кривим виглядати, що краще би був чистий ассемблер.

Пів року на спп не писав, і забув що тут 3 різних значення ключового слова статік.
Я мав но увазі статік поля классу, що треба вручну ініціалізувати в модулі компіляції...

Там теж мьютекс буде, якщо не static constexpr

А для цих мьютекс навіщо, якщо вони 1 раз ініціалізуются у глобал скоуп?

doc.riot-os.org/...​s__cxx__ctor__guards.html
Для прикладу:

#include <cstdio>
#include <thread>
#include <string>

class singleton final
{
public:
    static const singleton* instance() noexcept;
    const char* foo() const noexcept;
private:
    singleton() noexcept;
    std::string msg_;
};
const singleton* singleton::instance() noexcept
{
    static singleton _instance;
    return const_cast<singleton*>(&_instance);
}

singleton::singleton() noexcept:
  msg_("FOO") 
{}

const char* singleton::foo() const noexcept 
{
    return msg_.data();
}

int main(int argc, const char **argv) 
{  
   std::thread foo([](){ 
    // std::this_thread::yield();
    auto inst = singleton::instance();
    std::printf("%s\n", inst->foo());
  });
   std::thread bar([](){ 
    auto inst = singleton::instance();
    std::printf("%s\n", inst->foo());
  });
  bar.join();
  foo.join();
  return 0;
}
Відповідно : godbolt.org/z/8nen4q6dh
singleton::instance():
movzx eax, BYTE PTR guard variable for singleton::instance()::_instance[rip]
test al, al
je .L35
mov eax, OFFSET FLAT:singleton::instance()::_instance
ret
.L35:
sub rsp, 8
mov edi, OFFSET FLAT:guard variable for singleton::instance()::_instance
call __cxa_guard_acquire
test eax, eax
jne .L36
mov eax, OFFSET FLAT:singleton::instance()::_instance
add rsp, 8
ret
.L36:
mov eax, 20294
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:singleton::instance()::_instance
mov edi, OFFSET FLAT:singleton::~singleton() [complete object destructor]
mov QWORD PTR singleton::instance()::_instance[rip], OFFSET FLAT:singleton::instance()::_instance+16
mov WORD PTR singleton::instance()::_instance[rip+16], ax
mov BYTE PTR singleton::instance()::_instance[rip+18], 79
mov QWORD PTR singleton::instance()::_instance[rip+8], 3
mov BYTE PTR singleton::instance()::_instance[rip+19], 0
call __cxa_atexit
mov edi, OFFSET FLAT:guard variable for singleton::instance()::_instance
call __cxa_guard_release
mov eax, OFFSET FLAT:singleton::instance()::_instance
add rsp, 8
ret

От ABI для Andoroid android.googlesource.com/...​bc/bionic/__cxa_guard.cpp

    __futex_wait_ex(&gv->state, false, CONSTRUCTION_UNDERWAY_WITH_WAITER, false, nullptr);
    old_value = atomic_load_explicit(&gv->state, memory_order_relaxed);
Футекс це критична секція, тобто спінлок потім мьютекс
От ABI Intel github.com/...​/master/src/cxa_guard.cpp
int __cxa_guard_acquire(guard_type* guard_object)
{
char* initialized = (char*)guard_object;
if (pthread_mutex_lock(&guard_mut))
abort_message("__cxa_guard_acquire failed to acquire mutex");
int result = *initialized == 0;
if (result)
{
#if defined(__APPLE__) && !defined(__arm__)
const lock_type id = pthread_mach_thread_np(pthread_self());
lock_type lock = get_lock(*guard_object);
if (lock)
{
// if this thread set lock for this same guard_object, abort
if (lock == id)
abort_message("__cxa_guard_acquire detected deadlock");
do
{
if (pthread_cond_wait(&guard_cv, &guard_mut))
abort_message("__cxa_guard_acquire condition variable wait failed");
lock = get_lock(*guard_object);
} while (lock);
result = !is_initialized(guard_object);
if (result)
set_lock(*guard_object, id);
}
else
set_lock(*guard_object, id);
#else // !__APPLE__ || __arm__
while (get_lock(*guard_object))
if (pthread_cond_wait(&guard_cv, &guard_mut))
abort_message("__cxa_guard_acquire condition variable wait failed");
result = *initialized == 0;
if (result)
set_lock(*guard_object, true);
#endif // !__APPLE__ || __arm__
}
if (pthread_mutex_unlock(&guard_mut))
abort_message("__cxa_guard_acquire failed to release mutex");
return result;
}
void __cxa_guard_release(guard_type* guard_object)
{
if (pthread_mutex_lock(&guard_mut))
abort_message("__cxa_guard_release failed to acquire mutex");
*guard_object = 0;
set_initialized(guard_object);
if (pthread_mutex_unlock(&guard_mut))
abort_message("__cxa_guard_release failed to release mutex");
if (pthread_cond_broadcast(&guard_cv))
abort_message("__cxa_guard_release failed to broadcast condition variable");
}

Тут навіть не просто мьютекс — а conditional variable з флагом.

Чому це в стандарті пішло з цієї статті Скотта Маєрса та Андрія Александреску, www.aristeia.com/...​_Jul_Aug_2004_revised.pdf 2004 року, одних із найвідоміших гуру С++, авторів книг та засновників Modern C++ як такого, разом із Герб Саттером, та іншими хто одними з перших започаткував комітет із стандартизації С++ разом із Бйорном Страуструпом.

Я трохи за інші статики питаю.
Ви навели приклад статік локал варієбл.

const singleton* singleton::instance() noexcept
{
static singleton _instance;
return const_cast(&_instance);
}

Тут все зрозуміло.

Я питав за інший тип статіків.

class Single {
public:
static Single* s;
};

...
Single* Single::s = new Single();

//Таке от навіщо м,ютити ?

З тих самих причин. Щоби два чи більше потоків не ініціалізували одночасно і не було Race Condition, як в компіляторах стандарта С++ 98 зі статичними змінними, типу Visual C++ 6.0 чи Borland C++ 5.0/C++ Builder.
Читайте статтю 2004 року там усе написано, саме тоді з’явивились IBM PowerPC 970MP (на яких працювали Mac та Nintendo Wii — RISC нащодок Motorola та MIPS) та трохи пізніше Pentium D — тобто пенек 4 з мультісреднігом і двома ядрами і AMD Opeteron (двоядерний Atlon), та це усе стало актуально разом із атомарними перемінними compare exсhange і т.д.
До цього моменту, більшість коду і на С++ писали так само як на пайтоні із GIL — однопоточним. У Java до 1.6 та інших — були не потоки ос, а файбери (корутини, async/await і т.д.) і т.д.

Звідки візмутся ці декілька потоків ?
Ціж статік поля классу є декорацією для глобал варієйблс.
Все це ініціалізуєтся до початку main() звідки візмутся різні потоки на момент іх ініціалізації ?

Ніхтож не лочить ініціалізацію глобальних змінних ?

Так згадав цей прикол.
Я пам,ятаю що колись на співбесіді обговорювали, що воно не обов,язково на м,ютексах повинно бути.
Тіж самі спін локи можна заюзати.

В дронобудуванні чи не все на С++

Не не не.
Це все эмбеддед, де чистый С, або порізаний C++ до не впізнаваємості.

Мене, в ємбеддед С++ навіть на першу співбесіду не брали, хоча С++ досвід 15 років.

ближче до pure С ніж до C++, я так розумію що розвиток в embedded йшов знизу зі сторони заліза, і ті хто приходили в розробку не дуже заморочувались те що С і С++ це різні мови

Для мене С++ це C з RAAI в основному. Ну, з шаблонами. Класи, але без фанатизму.

Окрім мовних розширень, я б відрізняв тому що має свій окремий тулчейн.
p.s. якщо бачу що бінарник ліби через dlopen юзає то скоріш з c++ зібраний (чи схожих по концептам мов)

Та який ембедед? Там або лінукс на Raspberry/Orange/Jetson, або STM32

Для тих хто зі сторони класичного ос софта приходить часто всі iot розглядають як ембедед. Часто тому що треба програмувати io, а так то так є — який небудь rock64 напевно крутіш ніж у мене комп був 10років тому.

веб з парсінгом джейсонів давно в ембедед

У меня другое предложение — забрать С++ у теоретиков из комитета и отдать в руки любой крупной софтварной корпорации.

Так там їх давно вже немає — isocpp.org/std/the-committee. В основному Гугл, Майкрософт, Епл, Нвідія, Інтел, Тойота. Чи ви маєте на увазі забрати і віддати комусь одному з них?

Приклади в статті бомбезні. Сісти і плакати.

Там как раз копрпорации вошли внутрь и стали между собой воевать за продвижения того что им нужно и получился колпас.

упаси бог, даже с комитетом если отбились от предложения Google, которое ломало язык и давало им там в каком-то проекте 0.7% прироста производительности

Ну ясно, з рештою Google задовбало, що їх постійно гинуть в комітеті по стандартизації, равно як і NVidea.

> Хейтять раст
> Лізуть зі шкіри щоб перетворити С++ на раст (не виходить)

Там абсолютно якась бюрократія відбувається нажаль в комітеті, ті же пули потоків не можуть прийняти вже 7 років. Блокують усі пропозиції Google, просто тому що це Google і т.д. Бйорна це напевно вже дістало.
Так проблема — будь якої комісії, це завжди політика яка призводить до блокування конструктивних пропозицій за яку більшість, на конфлікті вимоги меншості. Можемо взяти ЄС — Угорщина і Словаччина проти усіх інших і усе.

Гугль — не бідна контора, багато чого могла б соді дозволити. Хочеш — Дарт допилюй, хочеш — на Раст переходь, хочеш — Карбон винаходь. Але ні, чогось все ще жують цей кактус і бодаються з комітетом плюсів. І будуть робити це й надалі. Тому, що свій власний винахід ти будеш тягнути сам, і це буде відображатися на всьому — якості компіляторів, кількості бібліотек, досвіді програмістів. Ну от і торгуються з меншістю, як можуть.

Тому, що свій власний винахід ти будеш тягнути
сам,

Гошечка похоже прекрасно себя чувствует.

мабуть, що гошечка на Андроід погано кладеться, чи вже є який NDK/SDK?

Понятия не имею. Андроид за пределами моих интересов.

По суті що бодатися їм сенс є, то так. Але фокус поза нішами згаданих мов: у dart/flutter своя спеціалізація — фронтенди мультіплатформні, go — явно для бекендів розроблявся, rust — важко впроваджувати і поза бігкомпані не дуже того бажають. Тобто щоб отримати щось більше ніж вищезгадані нішеві варіанти в додаток до згаданого тягнути самому.

Але ні, чогось все ще жують цей кактус і бодаються з комітетом плюсів.

Лише в коді пошувика 10 мільйонів операторів на С++. Так само Youtube і т.д. Це вже занадто догого, реплатформінг насправді завжди починається із бізнесаналізу або реверс інженерингу інакеше він не ефективний. А так скомпілював і усе, тому ще є системи які і досі на COBOL. А отсучаснити мову рельно можна, це неоднаразово було доказано. Та в комітеті приблизно з 22 року стався колапс, він був на вколо того що треба зламати ABI і частково зламати зворотню сумісність, почали тему пердставники Google. Активно протидіяли — Microsoft, бо для них навпаки головний пріорітет зворотна сумісність. Замість знайдення балансу, усе пішло на протистояння.

Там абсолютно якась бюрократія відбувається нажаль в комітеті

потому что backward compatibility это фича, а не такая ***ня, как в питоне: был второй, стал третий и до сих пор, спустя 15 лет, это гавно расхлебываем

семь раз отмерь — один раз отрежь, потому что потом всю жизнь с этим будешь мучаться

Взагалі-то rust то і є покращений c++ (з точки зору Мозіли), для того і створювали раст щоб не проходити довгий шлях впровадження потрібних для браузера фіч в c++. Єдине що дуже важко щось те впровадження в їх код пішло.

Господі, вони стандарти нормально прийняти не можуть, а тут якийсь масивний апгрейд мови розробки...

sed 's/показчики/вказівники' $THIS_POST

Как нам сохранить актуальность языка?
— разрабатывать нормальные стандартные библиотеки, покрывающие широкий набор сценариев, а не наборы костылей которые устаревают за три года?
— улучшить синтаскис и читаемость существующих механизмов?
— ДОБАВИТЬ ПРОФИЛИ В КОМПИЛЯТОР!

разрабатывать нормальные стандартные библиотеки, покрывающие широкий набор сценариев, а не наборы костылей которые устаревают за три года?

зачем тратить время комитета на то, что порешает рынок через сторонние библиотеки?

Допоки на C++ мільярди строк коду — на дно він не піде.

Але гарно, якщо додадуть зміни, щоб зробити код безпечнішим. Головне, щоб це було опціонально — для забезпечення плавного переходу.

І просто зараз С++ значно популярніший за Rust. По усіх індексах — це друга за популярністю мова після Python. Існує міжнародний стандарт, принаймні 4 дуже популярних конкуруючих фронтендів компіляторів і т.д.

Ну, популятність — то таке. Скільки расту і скільки спп.

До того-ж, якщо ми обговорюємо якості, то популярність не завжди пов’язана з цим. Mersedes може і більш безпечна автівка, але на дорогах значно більше китайців та французів з зовсім іншого цінового сегменту. ;)

І, знову таки. Вплив на популярність кількості вже наявних строк коду значно більше, ніж наявність стандарту, фронтендів компіляторів і іншого. А строки коду — це, перш за все, час.

Повертаючись до ісходного питання, не так важливо, яким чином буде досягнута мета по підвищенню якості коду — шляхом переходу на іншу мову, чи шляхом вдосконалення існуючої. Головне, щоб це відбулося.

Це має сенс і це варто було зробити давно. В плюсах занадто багато способів відстрелити собі ногу або писати неоптимальний код.

Звісно, треба лишити існуючий функціонал, наприклад при роботі з bare metal і подібним.

и что, в следующем году возьмут и разом выбросят сотни миллионов написанных строк кода для проектов типа Chrome? все игровые движки? операционные системы?

Старий код буде працювати як працював. Просто в новому коді буде більше автоматичних перевірок.

я это все понимаю, я про грядущий дедлайн в 26-м году

— Скоріш усього так як і раніш колись:
коли в 77році рекомендували перейти на safe languages, а в 91му вимагали використовувати safe ada.
Тобто в цьому випадку — зараз знаходимось в 76році.

— Ще одну аналогію можна провести з трохи більш недавнім комерційним проштовхуванням java (safe language який мав замінити всі інші «не» safe).

Якщо брати аналогію з першим випадком то вимоги у використанні пройдуть через декілька років після дедлайн, якщо з другим — то з’явиться ще одна ніша (для комерційних продуктів на базі нп rust) приблизно така як у java сьогодні.

Ага. А потом понадобится компилировать старый и новый код в рамках одного проекта.

Підписатись на коментарі