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

Ещё о печати строк в SectorC (лонгрид)

Недавно познакомился с интересным компилятором подмножества языка Си — SectorC. Он интересен тем, что влезает в один сектор (512 байт), но позволяет писать вполне реальные программы. В реализованном языке очень многого нет, тем не менее, это очень интересное достижение и мне было интересно поразбираться как всё сделано. Кстати, результат автора мне удалось в несколько приёмов уменьшить на несколько байт.

Из-за ограниченного синтаксиса в язык ни в каком качестве не влезли строки, хотя в стандартной библиотеке языка есть способ вывести символ по его коду. Я уже делал попытку как-то упростить себе жизнь, когда писал «Песню о пиве» и мне понадобилось выводить последовательность символов, но почти сразу после этого мне пришла в голову идея получше.

Для того, чтобы рассказать в чём смысл, надо для начала немного углубиться в то как работает этот компилятор.

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

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

Отсюда следует, что последовательно называющиеся переменные — a, b, c и так далее, с будут в памяти располагаться последовательно со смещением в два байта, точностью до переполнения. При этом их даже не обязательно определять — переменная с любым именем существует с начальным нулевым значением.

Идём дальше.

В языке есть любопытная операция — &, позволяющая получить адрес переменной, который, напомню, равен одновременно хешу (atoi) к её имени. Для однобуквенных имён это позволяет по полученному адресу восстановить код символа — надо поделить полученное значение на два и добавить 48 — код символа 0.

Таким образом, передав в функцию адреса & H, & e, & l, & l, & o, мы сможем восстановить из них символы и составить из них слово Hello. Если записать эти адреса в переменные, которые располагаются в памяти подряд, можно, читая память последовательно, выводить закодированную строку.

Есть ли в языке способ прочитать что-то по адресу? Как ни странно, есть — токен *(int*) позволяющий это сделать.

Теперь всё вышесказанное можно соединить в следующий довольно компактный код:

void print_str()
{
    print_str_pointer = & _0;
    while( *(int*) print_str_pointer ){
        print_ch = ( *(int*) print_str_pointer >> 1 ) + 48; print_char();
        print_str_pointer = print_str_pointer + 2;
    }
}

void main()
{
    _0 = & H; _1 = & e; _2 = & l; _3 = & l; _4 = & o; _5 = 0;
    print_str(); // Hello
}