• XSS.stack #1 – первый литературный журнал от юзеров форума

Wstring - Кастомная реализация Wchar_t строк на чистом C++

X-Shar

(L2) cache
Пользователь
Регистрация
17.05.2020
Сообщения
473
Реакции
354
Всем привет !

Для чего нужен данный проект:

1)Для создания протекторов, зверьков и если по какой-то причине нужно отказаться от STL и CRT.

2)Также полезно для тех-кто пишит драйвера на уровне ядра.

Что это за проект:

Кастомная реализация работы со строками wchar_t, написана на чистом С++, это означает, что не используются сторонние библиотеки в частности STL и либси.
Даже оператор new, был заменен на кастомный алокатор.)))

Итак, что умеет данный класс:

1)Удобное сравнение строк в условных операторах (<,>,==).

2)Удобное создание строк, пример:

Wstring s = L" Тестовая Строка. ";

3)Сложение строк:

Wstring test1 = s1 + s2 + s3;

4)Разделение строки на лексемы (Метод split), как использовать:

4.1)Создаете массив строк, в который будут помещены лексемы:

Wstring test_split[10];

//Для наглядности тестовая строка:
Wstring tested_str = L"Test1|Test2|Test3|Test4|Test5";

4.2)Вызываете метод split,с необходимыми параметрами (Описание параметров ниже):

tested_str.split((wchar_t*)L"|", test_split, 10, &count_splited);

Описание параметров:

(wchar_t*)L"|" - Делитель.

test_split - Массив, куда будут помещены итоговые строки.

10 - Размер массива.

count_splited - Число полученных строк.

Все в test_split, будут итоговые строки.)))

5)Метод c_wstr() - Получение указателя на wchar_t.

6)Метод c_str() - Получение указателя на char. Т.е. конвертация wchar_t* в char*.
ВАЖНО, после получения такого указателя, его нужно освободить вручную, вызвав hFree.

7)Метод Find, выполняет поиск подстроки в строке.

Возвращает позицию искомой строки.

Пример:

Wstring test_find = L"This is an awesome fun project. Let's do this again.";
size_t npos = test_find.Find(L"awesome");

8)Метод numeric_to_wstr - Конвертирует число в строку.

Пример:

//Тест конвертации числа в строку
uint32_t npos = 55;
Wstring Position_str = L"\0";
Position_str = Position_str.numeric_to_wstr(npos);

Описание проекта:

Wstring.cpp - Основная реализация класса.

Helpers.cpp - Кастомная реализация работы со строками из либ. си.

Test.cpp - Тестовые примеры, как работать.

Release/Wstring.exe - Собранный проект.

Ссылка на гитхаб:https://github.com/XShar/Custom_Wchar_String
 
Не чистый C++, а жиденький Си с классами. Начнём с того, что класс и функции могли бы быть шаблонными, тогда можно было бы указать и свой аллокатор с кастомным new и delete, и пофиг было бы, какой там тип символов, char, wchar_t, char16_t, char32_t или что-то ещё. Можно было бы скомпилировать для любого типа.

По порядку:
  1. Helpers.h
    1. Зачем там инклюд Windows.h?
    2. Для size_t не хватает инклюда, это stddef.h.
    3. Зачем называть функции типа my_memset, если можно просто всю эту бадягу обернуть в какой-нибудь неймспейс? Тогда и функции нормально назвать можно было бы.
    4. strCmpW должна принимать указатели на константные буферы.
    5. Я не говорю о том, что практически все функции небезопасны: ни одна из них не защищена от переполнения буфера. Код в худших традициях старого Си, где можно вызовом любой взятой функции выстрелить себе в ногу.
  2. Helpers.cpp
    1. Инклюда Helpers.h не хватает.
    2. Дефайны, когда есть возможность сделать это всё нормальными константами в анонимном пространстве имен.
    3. Весь файл выглядит как копипаста по кускам из разных источников. Тот же убогий BITOP взят со stackoverflow. Си-style преобразования типов.
    4. strTokW непотокобезопасен и не реентрабелен. Это прямо 10/10 в нашем современном многопоточном мире. Как и, получается, Wstring::split.
  3. Wstring.h
    1. Конструктор из wchar_t* должен быть explicit.
    2. operator+, operator=, operator<, operator> должны быть константными функциями. Для operator[] должна быть константная версия.
    3. Отсутствуют конструктор перемещения и перемещающий оператор присваивания. Напомню, этой функциональности C++ уже почти 10 лет, пора бы и изучить.
    4. Половина функций называются в CamelCase, другая половина в underscore_style.
    5. Почему delim в split не константный?
    6. numeric_to_wstr, split, Find следует сделать static.
    7. c_str(), после которой требуется подчищать память, это вообще п*ц. Можно было бы обернуть в unique_ptr, он не требует ничего из crt, или в худшем случае обернуть в самопальный умный указатель.
    8. operator== должен принимать указатель на константный буфер wchar_t.
  4. Wstring.cpp
    1. npos можно сделать constexpr и поместить в анонимное пространство имен вместо static. Будем современными.
    2. В конструкторе следует использовать списки инициализации для членов класса. Впрочем, по умолчанию их можно было бы проинициализировать прямо в заголовочном файле.
    3. ОГО я вижу nullptr - лучик света из C++11 в беспробудном аду C++98 вперемешку с Си!
    4. Зачем каст значения strLenW к size_t? Она и так size_t возвращает.
    5. В строке 15 возвращаемое значение hAlloc кастится к wchar_t*, и хопа, имеем неопределенное поведение, потому что lifetime для буфера никто не начал. Такая же фигня есть в Resize и еще во многих других местах.
    6. sizeof(wchar_t)*this->length + 1 - ещё одно неопределенное поведение, переполнение буфера. Завершающий нулевой символ в wchar_t занимает не 1 байт, а sizeof(wchar_t) байтов (в Windows это 2).
    7. В операторах присваивания не освобождается буфер, который уже мог храниться в this->data. Правильно, а зачем? Оперативной памяти сейчас ведь у всех много, пусть утекает! То же самое происходит в operator+.
    8. Есть копипаст. Ветку if (n == 0) в Resize() можно было бы заменить на единственный вызов Clear().
    9. Ветка else if (n < length) в Resize должна урезать строку, а не оставлять всё как есть. Именно это делает std::basic_string.
    10. IsEmpty можно пометить noexcept.
    11. Вызов operator==, operator<, operator> приведет к падению программы, если строку никто не проинициализировал (вызвали конструктор по умолчанию, т.е. строка пустая). Падение будет внутри strCmpW, когда туда передадут нулевой указатель this->data или s.data.
    12. c_str() вызовет падение программы, если вызвать его на пустой строке.
    13. c_str() вообще такой ущербный, что про него можно еще писать и писать. Например, он конвертирует в UTF8, а может, я хочу что-то другое? Он, например, еще выделяет буфер под сконвертированную строку неправильно. Он не проверяет возвращаемое значение WideCharToMultiByte. Это просто сборник всех худших ошибок студента первого курса на факультете программирования.
    14. Метод Find в целом почти такой же убогий, как и c_str(). Тут есть вызов strLenW во внутреннем цикле КАЖДУЮ ИТЕРАЦИЮ (сложность алгоритма увеличивается с обычной O(N*M) до O(N*M*M), где N = length, а M = strLenW(data). Скорость выполнения, соответственно, падает в M раз). Кстати, можно было бы применить какой-нибудь из алгоритмов быстрого поиска (Boyer-Moore, Knuth–Morris–Pratt, Rabin-Karp). Напомню, что в стандартной библиотеке C++ Boyer-Moore и Boyer-Moore-Horspool присутствуют уже три года.
    15. Зачем в numeric_to_wstr такой огромный буфер в 1024 байта?
Устал читать, кажется, у меня уже идет кровь из глаз. Нашел бы еще кучу ошибок и проблем, но, думаю, на этом можно пока остановиться.

Итого оценка по коду: 2/10 (2 - потому что ну хоть как-то он работает и собирается).

Теперь по функционалу:
  1. Нет конструктора строки с явным указанием размера. Соответственно, строка не сможет содержать нулевые байты, ее нельзя проинициализировать из строки с нулевыми байтами.
  2. Нет примера компиляции с отключенной CRT. Собирается проект всё равно с CRT, и никто, кажется, даже не проверял, будет ли это всё работать без CRT. У меня вот не собралось. В чём тогда вообще был смысл писать такую парашу, если основная цель так и осталась невыполненной?
  3. Код некроссплатформенный.
Функционал: 0/10 (цель компиляции без CRT не выполнена, в остальном эта строка хуже любой другой, нет смысла ее применять).
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Странный вопрос :\
Чтобы не тянуть за собой зависимость от STL
А зачем не тянуть с собой зависимость от STL? Это нужно только белым проектам. :)
 
В строке 15 возвращаемое значение hAlloc кастится к wchar_t*, и хопа, имеем неопределенное поведение, потому что lifetime для буфера никто не начал. Такая же фигня есть в Resize и еще во многих других местах.
Kaimi, что-то непойму а почему нельзя кастить в wchar_t* ?
 
Kaimi, что-то непойму а почему нельзя кастить в wchar_t* ?

Тема работы с сырой памятью в C++ достаточно сложна. Выделение памяти производится с помощью HeapAlloc. C++ ничего не знает об этой функции (что она выделяет память, пригодную для использования). Массивы (а код создает в итоге массив wchar_t) являются неявно создаваемыми объектами (implicit), и в принципе каст допустим, только вот если память была выделена одним из перечисленных тут способов: https://en.cppreference.com/w/cpp/language/object#Object_creation. Также, так как элементы массива (wchar_t) являются скалярными типами, их не нужно явно создавать или удалять в самом массиве, так что в этом плане всё ок. Если бы создавался массив нетривиальных объектов, то пришлось бы для каждого из них вызвать явно конструктор.

Чтобы C++ понял, что HeapAlloc выделяет хранилище под массив, можно сделать что-то подобное:
void* raw_memory = hAlloc(...);
this->data = new (raw_memory) [количество_элементов_массива];
Тут приходится вызывать placement new. Кроме того, придется прикапывать оба указателя, и удалять потом raw_memory, не трогая data.

Как вариант, можно было бы просто переопределить глобальные операторы new и delete и пользоваться ими.

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

Всем привет !

Для чего нужен данный проект:

1)Для создания протекторов, зверьков и если по какой-то причине нужно отказаться от STL и CRT.

2)Также полезно для тех-кто пишит драйвера на уровне ядра.

Что это за проект:

Кастомная реализация работы со строками wchar_t, написана на чистом С++, это означает, что не используются сторонние библиотеки в частности STL и либси.
Даже оператор new, был заменен на кастомный алокатор.)))

Итак, что умеет данный класс:

1)Удобное сравнение строк в условных операторах (<,>,==).

2)Удобное создание строк, пример:

Wstring s = L" Тестовая Строка. ";

3)Сложение строк:

Wstring test1 = s1 + s2 + s3;

4)Разделение строки на лексемы (Метод split), как использовать:

4.1)Создаете массив строк, в который будут помещены лексемы:

Wstring test_split[10];

//Для наглядности тестовая строка:
Wstring tested_str = L"Test1|Test2|Test3|Test4|Test5";

4.2)Вызываете метод split,с необходимыми параметрами (Описание параметров ниже):

tested_str.split((wchar_t*)L"|", test_split, 10, &count_splited);

Описание параметров:

(wchar_t*)L"|" - Делитель.

test_split - Массив, куда будут помещены итоговые строки.

10 - Размер массива.

count_splited - Число полученных строк.

Все в test_split, будут итоговые строки.)))

5)Метод c_wstr() - Получение указателя на wchar_t.

6)Метод c_str() - Получение указателя на char. Т.е. конвертация wchar_t* в char*.
ВАЖНО, после получения такого указателя, его нужно освободить вручную, вызвав hFree.

7)Метод Find, выполняет поиск подстроки в строке.

Возвращает позицию искомой строки.

Пример:

Wstring test_find = L"This is an awesome fun project. Let's do this again.";
size_t npos = test_find.Find(L"awesome");

8)Метод numeric_to_wstr - Конвертирует число в строку.

Пример:

//Тест конвертации числа в строку
uint32_t npos = 55;
Wstring Position_str = L"\0";
Position_str = Position_str.numeric_to_wstr(npos);

Описание проекта:

Wstring.cpp - Основная реализация класса.

Helpers.cpp - Кастомная реализация работы со строками из либ. си.

Test.cpp - Тестовые примеры, как работать.

Release/Wstring.exe - Собранный проект.

Ссылка на гитхаб:https://github.com/XShar/Custom_Wchar_String
1593255775600.png

Неа, не исправлена. Мы же делаем код кроссплатформенным? (я надеюсь) Не на всех системах sizeof(wchar_t) равен двум, перепиши хотя бы так: hAlloc(sizeof(wchar_t) * (this->length + 1)), либо используй char16_t.
P.S. просто хочу, чтобы проект стал лучше :)
P.S.S. был бы не против помочь
 
Kaimi если не надоело общаться с нубом, интересно ваше мнение еще.

В общем, начальника сделалЬ следующее (Репозиторий обновлен):

1)Благодаря Jeffs собрали без CRT:

1593258837544.png


2)СделялЬ свой аллокатор памяти, теперь проект полностью кроссплатформен и не вызывает никакие виндовые АПИ. (Тестовый пример не в счет).

Немного про аллокатор, он находится в файле CustomAlloc.cpp:


В секции BSS, выделяется статически память 2Мб. (static uint8_t memory[HEAP_SIZE];), для строк это более чем достаточно думаю.
Далее уже в этой памяти идет аллокация и освобождение...)))

3)Просмотрел код, везде теперь проверки на nullptr и программа не должна падать...

Сразу скажу, что я хочу получить от проекта:

1)Сборка без сторонних либ. и CRT, поэтому понятно, что тут не будет не исключений, не умных указателей и т.д.

2)Цель именно wchar_t, для работы с char полно библиотек в гите и смысла делать нет.

3)Да возможно в будущем можно использовать темплейты.

4)Код должен-быть понятен человеку с уровнем C/С++.

Да есть два уровня знаний C++:

1. Те-кто кодят на си, но знают какой-то базовый набор знаний С++, который кстати может и быть устаревшим уже (Ну это базовые понятия классов, наследование, полиморфизм и т.д.).

2. Те-кто постоянно кодят на С++. Тут я думаю все понятно.

Даже кстати в вакансиях это различают, это не я придумал, если что.

Вообще если кому интересен проект, от помощи в доработке не откажусь, делайте форк например.
Ну или пишите что поменять.

Только не надо говорить что надо переписывать все.)))

З.Ы. Как перестать быть говнокодером ???)))
 
Давай пиши в PM.)

Ну Kaimi я уважаю, читаю его блоги. В целом согласен с чем-то, правда что-то много он написал и критика очень резкая, может настроения не было.)))
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх