#prog #c #cpp #article
C Compilers Disprove Fermat’s Last Theorem
И продолжение: Compilers and Termination Revisited
TL;DR: Компилятор C++ может удалить цикл без побочных эффектов, не доказывая, что он завершается, ввиду forward progress guarantee, компилятор C11 может сделать то же самое для циклов, у которых условие не является константным выражением, ввиду §6.8.5.6, для более старых версий стандарта C без этой оговорки непонятно.
А, и это из-за бага в LLVM в своё время затронуло Rust (issue).
C Compilers Disprove Fermat’s Last Theorem
И продолжение: Compilers and Termination Revisited
TL;DR: Компилятор C++ может удалить цикл без побочных эффектов, не доказывая, что он завершается, ввиду forward progress guarantee, компилятор C11 может сделать то же самое для циклов, у которых условие не является константным выражением, ввиду §6.8.5.6, для более старых версий стандарта C без этой оговорки непонятно.
А, и это из-за бага в LLVM в своё время затронуло Rust (issue).
#prog #c #cpp #моё
Если вам вдруг понадобилось в рантайме выяснить, была ли ваша программа скомпилирована в режиме C или же C++, то вот непотокобезопасная функция, которая вернёт ответ в виде ноль-терминированной строки напрямую на большинстве систем:
...
Нет, всё-таки, как это действительно работает?
Первая строка объявляет переменную
Строковые литералы в C и C++ являются массивами
Что же касается второй строки... Что ж, тут надо разбираться.
Во-первых,
Во-вторых, в этой строчке
литерал символа имеет тип
Тут возникает вопрос, какой же размер у
Итак,
В-третьих, малоизвестный факт заключается в том, что в C и C++ в литерал символа можно записать больше одного символа, и называется это multi-character character literal. То, как именно эти литералы отображаются на численные значения, зависит от реализации — но нам это и не важно, потому что эти значения мы всё равно не используем. А вот что важно — так это то, что и в C, и в C++ такие литералы имеют тип
С учётом изложенного выше, а также того, что оператор деления и в C, и в C++ является левоассоциативным, можно подсчитать, какие же получаются индексы в функции. Для C это
5/4 - 4/2/4 = 1 - 2/4 = 1
, поэтому присваивание записывает конец строки аккурат после первого символа
5/1 - 4/2/1 = 5 - 2/1 = 3
, потому присваивание записывает ноль после второго
В третьей строке строка возвращается, и из-за объявленного типа массив деградирует до указателя на его первый элемент. Признаком конца строки в C является ноль-символ, поэтому все функции стандартной библиотеки считают возвращаемое значение строкой
Если вам вдруг понадобилось в рантайме выяснить, была ли ваша программа скомпилирована в режиме C или же C++, то вот непотокобезопасная функция, которая вернёт ответ в виде ноль-терминированной строки напрямую на большинстве систем:
Как же это работает? В отдельной переменной записывается строка, в нужную позицию записывается признак конца строки, а затем строка возвращается. Всем пока.
char const* get_language() {
static char s[] = "C++\?";
s[sizeof s/sizeof'C'-sizeof'++'/2/sizeof'\?']=0;
return s;
}
...
Нет, всё-таки, как это действительно работает?
Первая строка объявляет переменную
s
и присваивает ей значение. Переменная s
статическая, чтобы возвращаемый указатель не становился висячим сразу после возврата из функции.Строковые литералы в C и C++ являются массивами
char
-ов, включающими в себя неявно добавляемый на фазе трансляции терминирующий нуль-символ. s
объявлена, как переменная типа char[]
. Обычно тип в C(++) нужно выписывать целиком, но длину массива в объявлении переменной можно опустить, если её можно вывести из типа инициализирующего выражения — как в этом случае. Строка "C++\?"
имеет четыре символа (четыре, потому что \?
является escape-ом для ?
(потому что триграфы)), поэтому с учётом конечного нуля получаем, что s
имеет тип char[5]
.Что же касается второй строки... Что ж, тут надо разбираться.
Во-первых,
sizeof
является унарным оператором и, вообще говоря, не требует скобок. При этом у него приоритет выше, чем у арифметических выражений, поэтому в выражении ниже они и не требуются. Не то чтобы это было сильно важно для понимания, но не все об этом знают.Во-вторых, в этой строчке
sizeof
применяется к, в частности, литералу символа. Чему равно значение этого выражения? Казалось, это должно быть 1, поскольку стандарт C прямо диктует (а стандарт C++ вторит), что sizeof(char)
равен 1. Но! Тут мы применяем sizeof
к литералу символа, и тут кроется одно из отличий между C и C++, делающее эту функцию возможной:литерал символа имеет тип
int
в C и тип char
в C++.Тут возникает вопрос, какой же размер у
int
. Если вы программируете не под микроконтроллеры, то на вашей целевой системе тип int
наверняка имеет размер 4 байта. Строго говоря, это не всегда верно, поскольку размер int
является implementation defined, но это настолько распространённое и работающее на практике заблуждение, что после появления 64-битных систем размер int
остался на них 4 байта. Не смотря на то, что int
по замыслу является целым числом нативного для машины размера, смена размера int
на 64-битных платформах до 8 байт сломала бы слишком много программ.Итак,
sizeof'C'
и sizeof'\?'
возвращает 4 для C и 1 для C++. sizeof s
возвращают размер типа s
. Для массива это размер в байтах. Так как s
имеет тип char[5]
, sizeof s
возвращает 5. Но вот что такое sizeof'++'
? Нет, это не опечатка.В-третьих, малоизвестный факт заключается в том, что в C и C++ в литерал символа можно записать больше одного символа, и называется это multi-character character literal. То, как именно эти литералы отображаются на численные значения, зависит от реализации — но нам это и не важно, потому что эти значения мы всё равно не используем. А вот что важно — так это то, что и в C, и в C++ такие литералы имеют тип
int
. Так что sizeof'++'
возвращает таки 4.С учётом изложенного выше, а также того, что оператор деления и в C, и в C++ является левоассоциативным, можно подсчитать, какие же получаются индексы в функции. Для C это
5/4 - 4/2/4 = 1 - 2/4 = 1
, поэтому присваивание записывает конец строки аккурат после первого символа
C
. Для C++ же5/1 - 4/2/1 = 5 - 2/1 = 3
, потому присваивание записывает ноль после второго
+
.В третьей строке строка возвращается, и из-за объявленного типа массив деградирует до указателя на его первый элемент. Признаком конца строки в C является ноль-символ, поэтому все функции стандартной библиотеки считают возвращаемое значение строкой
"C"
в C и "C++"
в C++ соответственно. Как видите, всё очень просто.godbolt.org
Compiler Explorer
Compiler Explorer is an interactive online compiler which shows the assembly output of compiled C++, Rust, Go (and many more) code.
#prog #c #cpp #article
How to check if a pointer is in a range of memory
Нет, просто сравнение указателей не работает. И, кстати, из соответствующего параграфа стандарта становится понятно, почему в C нельзя иметь типы нулевого размера.
How to check if a pointer is in a range of memory
Нет, просто сравнение указателей не работает. И, кстати, из соответствующего параграфа стандарта становится понятно, почему в C нельзя иметь типы нулевого размера.
Microsoft News
How to check if a pointer is in a range of memory
Thanks to the C language standard, it's trickier than it seems.
#prog #python #c #abnormalprogramming #article
Writing a C compiler in 500 lines of Python
С небольшим нюансом: компиляция в WASM и, понятное дело, только весьма небольшого подмножества C. Ну и для достижения ограничения на количество строк компилятор однопроходной.
Writing a C compiler in 500 lines of Python
С небольшим нюансом: компиляция в WASM и, понятное дело, только весьма небольшого подмножества C. Ну и для достижения ограничения на количество строк компилятор однопроходной.
vgel.me
Writing a C compiler in 500 lines of Python
Blog about linguistics, programming, and my projects
#prog #c #itsec
В glibc выявили уязвимость, которая позволяет локально повысить права до root. Корень проблемы — очередной buffer overflow.
Что иронично, код, который внёс уязвимость, был нацелен на фикс другой проблемы.
В glibc выявили уязвимость, которая позволяет локально повысить права до root. Корень проблемы — очередной buffer overflow.
Что иронично, код, который внёс уязвимость, был нацелен на фикс другой проблемы.
www.opennet.ru
Уязвимость в glibc, позволяющая получить root-доступ в системе
Компания Qualys выявила опасную уязвимость (CVE-2023-6246) в стандартной Си-библиотеке Glibc, позволяющую через манипуляции с запуском SUID-приложений добиться выполнения своего кода с повышенными привилегиями. Исследователи смогли разработать рабочий эксплоит…
#prog #c #rust #article
Porting libyaml to Safe Rust: Some Thoughts
Или о том, как автор перевёл libyaml, сконвертированный c2rust, в идиоматичный Rust-код. Почему? Этот код используется как бекенд для serde-yaml и при этом существующий интерфейс поверх не давал информацию о положении частей входа вне ошибок.
The result is not just safer and easier, it is also just as performant
Автор также более подробно рассказывает о тонкостях реализации на C и о том, как они изменились с переходом на Rust.
Porting libyaml to Safe Rust: Some Thoughts
Или о том, как автор перевёл libyaml, сконвертированный c2rust, в идиоматичный Rust-код. Почему? Этот код используется как бекенд для serde-yaml и при этом существующий интерфейс поверх не давал информацию о положении частей входа вне ошибок.
The result is not just safer and easier, it is also just as performant
Автор также более подробно рассказывает о тонкостях реализации на C и о том, как они изменились с переходом на Rust.
simonask.github.io
libyaml-safer
#prog #c #article
Why Not Just Do Simple C++ RAII in C?
В последнее время есть предложения для того, чтобы добавить в C defer. Разумеется, эта конструкция избыточна, если в языке есть RAII. Статья показывает, почему "просто добавить RAII в C" не сработает.
Why Not Just Do Simple C++ RAII in C?
В последнее время есть предложения для того, чтобы добавить в C defer. Разумеется, эта конструкция избыточна, если в языке есть RAII. Статья показывает, почему "просто добавить RAII в C" не сработает.
The Pasture
Why Not Just Do Simple C++ RAII in C?
Ever since I finished publishing the “defer” paper and successfully defended it on its first go-around (it now has tentative approval to go to a Technical Sp...
#prog #c #shell #menacingopensource
🥜 Pnut: A Self-Compiling C Transpiler Targeting Human-Readable POSIX Shell
⬇️
🥜 Pnut: A Self-Compiling C Transpiler Targeting Human-Readable POSIX Shell
int sum(int* a, int len) {
int i, sum = 0;
for (i = 0; i < len; i += 1) {
sum += a[i];
}
return sum;
}
⬇️
#!/bin/sh
set -e -u
: $((sum = i = len = a = 0))
_sum() { let a $2; let len $3
let i; let sum
sum=0
i=0
while [ $i -lt $len ] ; do
: $((sum += _$((a + i))))
: $((i += 1))
done
: $(($1 = sum))
endlet $1 sum i len a
}
# Runtime library
# Local variables
__=0
__SP=0
let() { # $1: variable name, $2: value (optional)
: $((__SP += 1)) $((__$__SP=$1)) # Push
: $(($1=${2-0})) # Init
}
endlet() { # $1: return variable
# $2...: function local variables
__ret=$1 # Don't overwrite return value
: $((__tmp = $__ret))
while [ $# -ge 2 ]; do
: $(($2 = __$__SP)) $((__SP -= 1)); # Pop
shift;
done
: $(($__ret=__tmp)) # Restore return value
}
GitHub
GitHub - udem-dlteam/pnut: 🥜 A Self-Compiling C Transpiler Targeting Human-Readable POSIX Shell
🥜 A Self-Compiling C Transpiler Targeting Human-Readable POSIX Shell - udem-dlteam/pnut