LINUX.ORG.RU

То что сложно реализовать на других языках

 


0

3

Разбираюсь с библиотекой qsp и решил с вами поделится кусочком кода, а точнее реализация crc32. Автор кода, решил создать свой собственный crc32, на базе CRC-32/ISO-HDLC, взяв от туда таблицу, но изменил стартовое значение, и еще по мелочи:

int qspCRC(void *data, int len)
{
    unsigned char *ptr;
    int crc = 0;
    ptr = (unsigned char *)data;
    while (len--)
        crc = (qspCRCTable[(crc & 0xFF) ^ *ptr++] ^ crc >> 8) ^ 0xD202EF8D;
    return crc;
}

В чем прелесть: Использует int, но фактически работает как unsigned int. Вся логика на переполнение - и это как то работает, хотя вроде это UB. Автор кода крут (без шуток), я не спорю, но переписать это на любой другой язык, который будет контролировать переполнение - будет крайне сложно. Почему автор не использовал любой из стандартных алгоритмов crc, как он использовал библиотеку для regexp - загадка.

★★★★
Ответ на: комментарий от MOPKOBKA

не не выйдет, там компилятор ругается, что пытаются впихнуть в i32 больше чем в него влезает, т.е затык уже на уровне таблицы, дальше он будет ругаться на 0xD202EF8D, это я уже пол ночи экспериментировал

Silerus ★★★★
() автор топика
Последнее исправление: Silerus (всего исправлений: 1)

В смысле, это пример хорошего кода? Или пример того, как не надо писать? Или чтобы растоманы могли им пугать всех остальных? (растоманы, мотайте на ус)

Если там неявный эффект сдвига знакового бита (ты об этом?), то тогда растоманы порвут этот пример на тряпки

anonymous
()
Ответ на: комментарий от LongLiveUbuntu

мне надоело Qqsp запускать, из под wine, если собрать нативно под линукс - еще хуже работает. Т.к не было у бабы заботы, а завяла себе хомяка

Silerus ★★★★
() автор топика

Тут нет переполнения, и нет UB. Сишники такие сишники - возносят друг друга, при этом код прочитать не могут.

Автор кода крут (без шуток),

CRC написать может школьник, в чём крутость?

Круто было бы если бы там действительно было UB - тогда всю это QSP шарашку можно было записать в некомпетентные ламеры и запретить им писать код. Ну и конечно если бы там было UB, оно бы не работало, в чём ты сам мог бы убедиться хоть на godbolt, где оно выдавало бы разные результаты на разных архитектурах, компиляторах и версиях компилятора.

но переписать это на любой другой язык, который будет контролировать переполнение - будет крайне сложно

В нормальных языках есть явные конструкции, определяющие поведение при переполнении. Вот, например, rust - смотри сколько у i32 методов для арифметических операций (это при наличии обычного «плюса») - overflowing_, saturating_, checked_, unchecked_, bounded_, wrapping_, widening_*. Ты можешь явно сказать что тебе тут нужен wrapping, и оно будет переполняться, при этом без UB, и на x86 скомпилируется в ту самую одну инструкцию. Поэтому rust - язык программироания, и на нём действительно можно писать крутой код. А на C, как верно заметили, можно писать только такую вот дрисню, по которой даже сишники не могут сказать рабочая она или нет.

anonymous
()
Ответ на: комментарий от MOPKOBKA

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

Silerus ★★★★
() автор топика
Ответ на: комментарий от Silerus

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

Если ты про rust, то при переписывании этого на rust нужно ровно два каста вокруг сдвига. Таблицу совершенно не нужно задавать в signed. Алсо это можно гораздо красивее записать в одну строчку через reduce.

anonymous
()
Ответ на: комментарий от MOPKOBKA

Такова участь Rust-кода. Если не касты, то лайфтаймы прописывать надо.

Ничего подобного. Если код пестрит кастами, вы просто работаете с неправильными типами.

anonymous
()
Ответ на: комментарий от anonymous

и конечно если бы там было UB, оно бы не работало, в чём ты сам мог бы убедиться хоть на godbolt, где оно выдавало бы разные результаты на разных архитектурах

Вот только "разных архитектур" на годболте практически нет. Для всего самого интересного будет написано, что нет runner’а.

LamerOk ★★★★★
()

А причём тут языки? Есть разные инструкции процессора для signed shift right и для unsigned shift right. Почти во всех компилируемых языках есть signed и unsigned целые, и соответствующие типу операции битового сдвига. Где здесь нужно удивиться крутости автора, это же основы основ битовых операций?

А UB оно потому что целые числа не везде именно такие, two’s complement. Вон тут аж 5 способов представления целых со знаком есть:

https://en.wikipedia.org/wiki/Signed_number_representations#Comparison_table

neumond
()
Ответ на: комментарий от Silerus

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

Компилятор говорит что один тип нельзя использовать там где ожидается другой, а приведение типов - базовое и обязательное действие. Правда, всё ещё нужно понимать что при этом происходит, поэтому мне, вообще говоря, не нравится что в rust есть as, а должны быть только into/try_into, при этом с более лаконичным синтаксисом, а ещё лучше были бы try_shrink_to, truncate_to (для сужающих преобразований), expand_to (для расширяющих), bitcast_to (для конверсии между signed/unsigned), try_lose_precision_to (для конверсий f64 → f32 и float → int).

А так, кастить u8 (потому что на входе у тебя байт) в usize (потому что массивы им индексируются) - абсолютно нормально и при этом безопасно. Кастить между i32 и u32, потому что этот неадекватный алгоритм использует сдвиг знакового - тоже. Без кастов ты этот алгоритм не напишешь.

anonymous
()
Ответ на: комментарий от anonymous

не совсем, если я напишу let a: i32 =0xD202EF8D; он мне скажет что так делать нельзя, потому что одно не вмещается в другое. А потому я делаю 0xD202EF8Du32 as i32 - и вроде все как стало хорошо. Но что то мне кажется что это как то не так. Вот что меня смущает. Почему нельзя было использовать стандартный crc32 - я вообще не понимаю.

Silerus ★★★★
() автор топика
Ответ на: комментарий от Silerus

Но что то мне кажется что это как то не так

Компилятор всё правильно делает. В числе 0xD202EF8D выставлен 32-й бит (1 << 31), такое положительное число не влезает в i32. Если ты вместо hex напишешь -771559539 то компилятор не будет ругаться, т.к. число отрицательное и влезает в допустимый диапазон для i32.

В Rust целочисленные типы всегда с двоичным дополнением. Каст 0xD202EF8D_u32 в i32 ничего не поломает.

Casting between two integers of the same size (e.g. i32 -> u32) is a no-op (Rust uses 2’s complement for negative values of fixed integers)

rust-reference

numas13
()
Ответ на: комментарий от Silerus

не совсем, если я напишу let a: i32 =0xD202EF8D; он мне скажет что так делать нельзя, потому что одно не вмещается в другое. А потому я делаю 0xD202EF8Du32 as i32 - и вроде все как стало хорошо. Но что то мне кажется что это как то не так. Вот что меня смущает

Ну это депендс. В общем случае конечно константа должна быть того же типа что литерал. Если почему-то она должна далее интерпретироваться как i32, то каст логичнее сделать по месту. С другой стороны, может быть и так что константа определяет битовое представление знанового числа. Может дажа float’а. Тогда почему бы её не скастить при объявленнии, если для этого нет отдельного типа литерала.

anonymous
()
Ответ на: комментарий от zurg
fn qspCRC(data: &[u8]) -> i32 {
    data.iter().fold(0, |crc, b| { 
            let tbl  = qspCRCTable[((crc & 0xFF) as u8 ^ b) as usize];
            (tbl as i32^crc>>8) ^ 0xD202EF8Du32 as i32
        })
}

жуть ваще. для простых суммирований массива вводится «итерация» как понятие и fold - как типа «свертка». в алгоритм влез еще какой-то неявный b, который определили волшебным образом вместе с crc. кстати, а какой тип у crc тут?

сишный код(при правильной записи канеш) будет и лишен всяких мусорных доп. понятий и определять все вполне точно.

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

https://godbolt.org/z/WsKb799jr

там кстати если поставить язык rust, должен быть ваш код. но не уверен. не знак как их ссылки работают.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от Silerus

просто хорош уже писать i32 там, где должно быть u32.

исходный код на Си крив по определению, для полной корректности там везде должен быть unsigned.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

Выхлопной код один в один, учитывай что у Rust используется LLVM: https://godbolt.org/z/h3K5avbzE

жуть ваще. для простых суммирований массива вводится «итерация» как понятие и fold - как типа «свертка».

Так вся rust std написана, у него видно сильное влияение всяких ФП язычков.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 4)
Ответ на: комментарий от alysnix

Не поставлю, ты хотел сравнить Rust vs C, а не Clang vs GCC. Как видишь все эти fold сворачиваются в тот же код что и твой сишный.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от alysnix

Я уже видел. Был бы у Rust нормальный компилятор на базе gcc, выхлоп на Rust и выхлоп на С не отличался бы тоже, так же как не отличается выхлоп Clang для С и Rust.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

в расте код куда менее понятный, и с привлечением лишних сущностей.

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

короче, раст уходит грустно сосать писю у си++. Си торждествует, потрясая копьями, танцует пляску победы, и приносит благодарственную жертву богам.

alysnix ★★★
()
Ответ на: комментарий от Silerus

Ну раст тут обсудили уже, а с Go какие проблемы? Практически один-в-один код, только типы нужно расставить. Это только в сях принято играть в игру, читая код: «написавший этот код - а) не заметил баг, б) допустил баг, потому что плохо знает си в) это не баг, а намеренная *ботня, с помощью которой автор понтуется битодрочерством, или г) он просто идиот?»

anonymous
()
Ответ на: комментарий от alysnix

в расте код куда менее понятный, и с привлечением лишних сущностей.

Как я уже сказал, Rust под сильным влиянием ФП язычков, а там вместо циклов из С как раз все эти fold, map, filter. Для них это как for цикл написать, другой способ мыслить и писать код. Вся Rust std так написана, не нравятся им простые циклы.

зато сишный код - ... требует простейшего компилятора ...

Это да.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

а там вместо циклов из С как раз все эти fold, map, filter

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

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

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

fold под капотом самый обычный цикл.

    fn fold<B, F>(mut self, init: B, mut f: F) -> B
    where
        Self: Sized,
        F: FnMut(B, Self::Item) -> B,
    {
        let mut accum = init;
        while let Some(x) = self.next() {
            accum = f(accum, x);
        }
        accum
    }

Iterator::fold

Но да, сишников это ставит в ступор (особенно не знающих ФП идиомы).

numas13
()
Ответ на: комментарий от numas13

fold под капотом самый обычный цикл.

кто бы мог подумать???! :)

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

Просто уж в таких простейших циклах, как подсчет crc суммы блока, эти «фп идиомы» не нужны.

Они и не проще лексически, и визуально читаемы плохо, и замусоривают код ненужной семантикой.

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

Причем в сишечке фолды и итераторы можно сделать просто на макросах, если так приперло.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

Тебя же не заставляют писать с fold. Rust тем и хорош, что можно писать почти как на Си если проблемы с ФП.

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

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

Причем в сишечке фолды и итераторы можно сделать просто на макросах, если так приперло.

Пошла стадия торга?

numas13
()
Ответ на: комментарий от alysnix

Просто уж в таких простейших циклах, как подсчет crc суммы блока, эти «фп идиомы» не нужны.

Как раз там они и нужны.

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

Ну конечно же многострочный цикл предпочтительнее. Для луддитов.

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

Вот он пусть и пыхтит, вместо меня. Зачем мне его работу делать. Пусть во что хочет fold редуцирует, хоть tail-call

anonymous
()
Ответ на: комментарий от anonymous

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

fn qspCRC(data: &[u8]) -> i32 {
    data.iter().fold(0, |crc, b| { 
            let tbl  = qspCRCTable[((crc & 0xFF) as u8 ^ b) as usize];
            (tbl as i32^crc>>8) ^ 0xD202EF8Du32 as i32
        })
}

эквивалентный(почти) код на си

int qspCRC(uint8* ptr, uint len) {
    int crc = 0;
    while (len--)
        crc = (qspCRCTable[(crc & 0xFF) ^ *ptr++] ^ crc >> 8) 
              ^ 0xD202EF8D;
    return crc;
}

а теперь вопрос - где больше промежуточных переменных? в сишном коде это лишь crc. в растовом еще две переменных.

растовый код принимал сразу косорукую «ссылку на массив» &[u8], что не есть эквивалент void* - это есть «указатель на что угодно», отчего требовался еще один поинтер внутри сишной функции.

также в растовом коде длина не передается, а в сишном она есть.

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

Передавайте в растовую функцию нетипизированный адрес и длину, тогда имеет смысл сравнивать.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

((crc & 0xFF) as u8 ^ b) as usize

Кстати, а что вы все этот код повторяете?

Я вот раст не знаю нифига, но есть же приведение к u8, зачем ещё и битовая маска, для надёжности? Какие-то хитрые особенности раста, или просто карго-культ сишечки?

anonymous
()