Представьте, что у вас enum имеет один из элементов MyEnum::Variant10(u8, u8, u8), а все остальные MyEnum::Variant0-9(u8), память будет выделена так:
дискриминант (по умолчанию usize т.е. 4 или 8 байт)
3 байта Итого: 11 байт на adm64
Зачем вы дезинформируете людей? Во-первых, по умолчанию размер дискриминанта выбирается минимального размера, подходящего для хранения всех вариантов. Во-вторых, если бы бы удосужились действительно проверить этот код, то увидели бы, что он печатает 4:
Что поливается говном, если никакой реализации профилей безопасности пока не существует?
Идея, что код на C++ можно корректно автоматически анализировать по функции за раз, не требуя при этом аннотаций от программиста. В исходном коде на C++ попросту недостаточно для этого информации.
И вам бы это было понятно, если бы вы таки удосужились прочитать текст по ссылке.
Вы про то, что надо знать детали реализации slice, чтобы им пользоваться? В остальном вроде всё терпимо.
Ох, если бы только это.
nil
append в nil-слайс возвращает слайс с единственным значением, но попытка вставки в nil-мапу паникует. Функцию cap можно вызвать на слайсе, но не на мапе.
Для создания выделенного в куче значения можно использовать new. С одной стороны, это избыточно, поскольку утекающий указатель на литерал значения автоматически приведёт к выделению в куче. С другой стороны, это не ортогонально, потому что де-факто ссылочные типы - слайсы, мапы и каналы - создаются через псевдо-функцию make.
Имена переменных могут затенять встроенные идентификаторы:
В языке практически отсутствует иммутабельность. Из-за этого приходится выбирать между передачей по указателю (дёшево, но функция может поменять) и передачей по значению (функция не может поменять, но потенциально дорогая операция копирования), а ещё пилить костыли. Константы могут быть только в виде чисел и строк.
У каждого типа есть нулевое значение. При создании структуры можно опустить некоторые из полей, в том числе все. Неуказанные поля получают нулевые значения. Добавил новое поле? Ищи все места самостоятельно. И не забудь, что, если структура не приватная, то внешний код тоже может такую ерунду делать.
У интерфейсов тоже есть нулевое значение, nil, и это отличается от интерфейса, который хранит nil-значение:
package main
import "fmt"
type I interface {
f()
}
type C struct{}
func (c *C) f() {}
func main() {
var in I
fmt.Println(in == nil) // true
var c *C = nil
in = c
fmt.Println(in == nil) // false
}
Каналы могут быть в двух невалидных состояниях: nil и закрытом. Посылка и получение в nil-канал блокируются навечно, в то время как отправка в закрытый канал паникует, а получение из закрытого канала возвращает нулевое значение. Зачем разница в поведении - неясно.
defer вызывается в конце функции. Функции, а не лексической области видимости.
Одно ключевое слово for имеет 11 разных значений в зависимости от семантики. У четырёх из них поведение зависит от того, по какому типу итерация, но синтаксис при этом идентичный. А, и ещё можно итерироваться с одной переменной по слайсу и получить индексы вместо значений.
Для обработки ошибок используются множественные возвраты. Обычно принято, что возвращается значение и nil в случае нормального возврата, в случае ошибки же возвращается произвольное (обычно нулевое) значение и не-nil ошибка. Однако этой конвенции даже в стандартной библиотеке не везде следуют.
Если в пакете есть функция под названием init, то она будет молча вызвана при импорте этого пакета. При импорте пакета несколько раз функция init будет вызвана несколько раз.
По моему, как-то многовато надо в голове держать для так называемого простого языка.
Тем, что это - сишная идиома, проистекающая из сишной проблемы невозможности автоматически вызвать некую функцию при выходе потока исполнения из заданного лексического скоупа. Как я уже писал, в других ЯП для этого есть более адекватные средства.
В общем, property-based testing — это эксперимент, который надо уметь ставить, и его результаты тоже надо уметь обрабатывать. Об этом прекрасно рассказал автор QuickCheck в видео John Hughes — Building on developers' intuitions (...) | Lambda Days 19.
Понимаю, что статья давняя, но можете вставить ссылку на искомое видео, чтобы читателям не пришлось искать самостоятельно?
Значит, вы ни разу в жизни не пробовали писать эффективные
стейтмашины
парсеры
Как-то выходит без этого. И да, в парсерах для эффективности стоит переходить с написания отдельного лексера и собственно парсера на паерсер, который меняет состояние по отдельным байтам, но руками писать такое сродни безумию.
зависимое выделение/освобождение ресурсов
А вот использование goto для освобождения ресурсов - это так костыль в сишечке. В практически всех языках для этого есть более вменяемые инструменты: RAII в C++/D/Rust, try-with-resources в Java, using в C#, context managers в Python, bracket pattern в StandardML/Ocaml/Haskell. Причём конкретно RAII напишет ровно такой же код, что и сишник руками, только без ошибок.
Проблема в том, что это не дерево. Более того, с учётом нулевых возможностей для сокрытия определений в Python и его динамических возможностей новые связи в этот граф могут быть добавлены произвольным внешним кодом и даже в зависимости от рантайм-данных.
Зато если запустить санитайзер gcc, вот он вдоволь срабатывает.
Если вы именно санитайзер имели в виду, то тут как раз нет ничего удивительного: статические анализаторы и динамические детектируют разные баги, с довольно небольшим пересечением.
Для Rust было бы неплохо. Есть Clippy, но там, ЕМНИП, нет никаких кросс-функциональных вариантов анализа, только в пределах одной функции. Ну и некоторые ошибки, которые PVS-studio детектирует, от языка слабо зависят (вроде V6004)
хотя большие языковые модели уже на пятки наступают
Они никогда не заменят статические анализаторы, потому что: а) никакая модель, какой бы навороченной она бы не была, не может диагностировать паттерны ошибок, которых не было в обучающих данных, и добавить новое правило в анализатор проще, чем дообучать модель на многих примерах. б) LLM склонны к галлюцинациям, причём довольно убедительно выглядящим, а от качественного статанализатора ждут низкого false positive rate.
Используйте языки с RAII
дискриминант (по умолчанию usize т.е. 4 или 8 байт)
3 байта Итого: 11 байт на adm64
Зачем вы дезинформируете людей? Во-первых, по умолчанию размер дискриминанта выбирается минимального размера, подходящего для хранения всех вариантов. Во-вторых, если бы бы удосужились действительно проверить этот код, то увидели бы, что он печатает 4:
Что обидно, поскольку конкретно приведённый фрагмент прям напрашивается на переписывание на сумм-типы
А, простите, зачем?
Идея, что код на C++ можно корректно автоматически анализировать по функции за раз, не требуя при этом аннотаций от программиста. В исходном коде на C++ попросту недостаточно для этого информации.
И вам бы это было понятно, если бы вы таки удосужились прочитать текст по ссылке.
И правильно делают, потому что профили в том виде, в котором их продвигают, не работают и принципиально работать не могут.
Ох, если бы только это.
nil
append
вnil
-слайс возвращает слайс с единственным значением, но попытка вставки вnil
-мапу паникует. Функциюcap
можно вызвать на слайсе, но не на мапе.Для создания выделенного в куче значения можно использовать
new
. С одной стороны, это избыточно, поскольку утекающий указатель на литерал значения автоматически приведёт к выделению в куче. С другой стороны, это не ортогонально, потому что де-факто ссылочные типы - слайсы, мапы и каналы - создаются через псевдо-функциюmake
.Имена переменных могут затенять встроенные идентификаторы:
В языке практически отсутствует иммутабельность. Из-за этого приходится выбирать между передачей по указателю (дёшево, но функция может поменять) и передачей по значению (функция не может поменять, но потенциально дорогая операция копирования), а ещё пилить костыли. Константы могут быть только в виде чисел и строк.
У каждого типа есть нулевое значение. При создании структуры можно опустить некоторые из полей, в том числе все. Неуказанные поля получают нулевые значения. Добавил новое поле? Ищи все места самостоятельно. И не забудь, что, если структура не приватная, то внешний код тоже может такую ерунду делать.
У интерфейсов тоже есть нулевое значение,
nil
, и это отличается от интерфейса, который хранитnil
-значение:Переменные цикла переиспользуются между итерациями, что в своё время привело к отзыву трёх миллионов сертификатов Let's Encrypt. Сейчас это поправили, но более разумное поведение, ЕМНИП, всё ещё opt-in.
Каналы могут быть в двух невалидных состояниях:
nil
и закрытом. Посылка и получение вnil
-канал блокируются навечно, в то время как отправка в закрытый канал паникует, а получение из закрытого канала возвращает нулевое значение. Зачем разница в поведении - неясно.defer
вызывается в конце функции. Функции, а не лексической области видимости.Одно ключевое слово
for
имеет 11 разных значений в зависимости от семантики. У четырёх из них поведение зависит от того, по какому типу итерация, но синтаксис при этом идентичный. А, и ещё можно итерироваться с одной переменной по слайсу и получить индексы вместо значений.Для обработки ошибок используются множественные возвраты. Обычно принято, что возвращается значение и
nil
в случае нормального возврата, в случае ошибки же возвращается произвольное (обычно нулевое) значение и не-nil
ошибка. Однако этой конвенции даже в стандартной библиотеке не везде следуют.Если в пакете есть функция под названием
init
, то она будет молча вызвана при импорте этого пакета. При импорте пакета несколько раз функцияinit
будет вызвана несколько раз.По моему, как-то многовато надо в голове держать для так называемого простого языка.
Тем, что это - сишная идиома, проистекающая из сишной проблемы невозможности автоматически вызвать некую функцию при выходе потока исполнения из заданного лексического скоупа. Как я уже писал, в других ЯП для этого есть более адекватные средства.
Только
[[maybe_unused]]
ещё докинуть, чтобы компилятор на определение гуарда не ругался.А этот антипаттерн от языка не зависит
Понимаю, что статья давняя, но можете вставить ссылку на искомое видео, чтобы читателям не пришлось искать самостоятельно?
Как-то выходит без этого. И да, в парсерах для эффективности стоит переходить с написания отдельного лексера и собственно парсера на паерсер, который меняет состояние по отдельным байтам, но руками писать такое сродни безумию.
А вот использование goto для освобождения ресурсов - это так костыль в сишечке. В практически всех языках для этого есть более вменяемые инструменты: RAII в C++/D/Rust, try-with-resources в Java, using в C#, context managers в Python, bracket pattern в StandardML/Ocaml/Haskell. Причём конкретно RAII напишет ровно такой же код, что и сишник руками, только без ошибок.
Проблема в том, что это не дерево. Более того, с учётом нулевых возможностей для сокрытия определений в Python и его динамических возможностей новые связи в этот граф могут быть добавлены произвольным внешним кодом и даже в зависимости от рантайм-данных.
Разбиение по парам зачем-то написано в виде tt-muncher, и это при том, что его можно написать напрямую:
Если вы именно санитайзер имели в виду, то тут как раз нет ничего удивительного: статические анализаторы и динамические детектируют разные баги, с довольно небольшим пересечением.
Для Rust было бы неплохо. Есть Clippy, но там, ЕМНИП, нет никаких кросс-функциональных вариантов анализа, только в пределах одной функции. Ну и некоторые ошибки, которые PVS-studio детектирует, от языка слабо зависят (вроде V6004)
Они никогда не заменят статические анализаторы, потому что:
а) никакая модель, какой бы навороченной она бы не была, не может диагностировать паттерны ошибок, которых не было в обучающих данных, и добавить новое правило в анализатор проще, чем дообучать модель на многих примерах.
б) LLM склонны к галлюцинациям, причём довольно убедительно выглядящим, а от качественного статанализатора ждут низкого false positive rate.
Ну да, опыт показывает, что они ещё и просто криво работают.
То, что MinGW пытается имитировать unix-like окружение на винде
Тем временем возможности C
Нет, дело именно в плюсах. Это настолько кривой язык, что на нём практически невозможно писать безопасно, даже если очень хочется