
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.75] |
![]() |
|
![]() |
|
|
Здравствуйте!
В программе перевода в двоичное число использую 2 функции: ![]() ![]() #include <stdio.h> #include <stdlib.h> #include <string.h> template <class T> void TypeToBin(T source, char* buffer, int size) { buffer += size - 1; for (int i = size - 1; i >= 0; i--) { *buffer-- = (source & 1) + '0'; // '0' - 48 '1' - 49 printf("%p\n", buffer); source >>= 1; } } template <class T> char* TypeToBin2(T source) { int const size = 8 * sizeof(T); char *p = (char*) malloc(size + 1); p[size] = '\0'; p += size - 1; for (int i = size - 1; i >= 0; i--) { *p-- = (source & 1) + '0'; // '0' - 48 '1' - 49 printf("%p\n", buffer); source >>= 1; } return p; } int main() { char a = 'a'; //int a = 1; int const size = 8 * sizeof(a); char buffer[size + 1]; buffer[size] = '\0'; TypeToBin(a, buffer, size); printf("a = %s strlen = %d\n", buffer, (int) strlen(buffer)); printf("a = %s\n", TypeToBin2(a)); printf("\n"); system("pause"); return EXIT_SUCCESS; } Прикреплённый файл ![]() |
![]() |
Сообщ.
#2
,
|
|
Ну так у тебя p постинкрементится, чтобы в начале следующей итерации указывать на очередной свободный символ, но когда цикл завершается, это свободное место не используется, и там остаётся мусор. Замени, например, на прединкремент, чтобы позиция свободного символа вычислялась на текущей итерации, а не предыдущей.
![]() ![]() p += size; for (int i = size - 1; i >= 0; i--) { *--p = (source & 1) + '0'; // '0' - 48 '1' - 49 printf("%p\n", buffer); source >>= 1; } |
Сообщ.
#3
,
|
|
|
Спасибо большое за ответ. Так получилось. Но возникли следующие вопросы:
1. Почему в первой функции такого не происходит? Из-за фиксированного размера буфера? 2. В строке *--p = (source & 1) + '0' или в строке p[size] = '\0'; появляется предупреждение VC++ "Разыменование пустого указателя, но все работает нормально. Что нужно сделать, чтобы это предупреждение исчезло? |
Сообщ.
#4
,
|
|
|
Разобрался по своему глупому 1 вопросу - потому что не возвращаю указатель. Если его возвращать, то результат тот же самый. А вот по вопросу № 2 не разобрался.
|
![]() |
Сообщ.
#5
,
|
|
Формально malloc() может вернуть NULL. Подозреваю, что ему не нравится тот факт, что нет на это поверки
Добавлено P.S. Вторая функция в целом плохо спроектирована. Во-первых, динамический буфер от malloc() должен освобождаться. Помимо того, что я нигде не вижу free(), а увидеть я его мог бы только на вызывающей стороно, т.е. в данном случае в main(), так ещё и вызываемому коду неоткуда взять параметр для free(), т.к. ей приходит не тот же указатель, что вернула malloc(), а насколько-то там увеличенный. |
Сообщ.
#6
,
|
|
|
Переделал функции:
![]() ![]() template <class T> void TypeToBin1(T source, char* buffer, int size) { buffer += size; for (int i = size - 1; i >= 0; i--) { *--buffer = (source & 1) + '0'; // '0' - 48 '1' - 49 printf("%p\n", buffer); source >>= 1; } } template <class T> void TypeToBin2(T source, char* buffer, int size) { for (int i = size - 1; i >= 0; i--) { buffer[i] = (source & 1) + '0'; source >>= 1; } } ![]() ![]() template <class T> char* TypeToBin3(T source) { int const size = 8 * sizeof(T); char buffer[size + 1]; buffer[size] = '\0'; for (int i = size - 1; i >= 0; i--) { buffer[i] = (source & 1) + '0'; source >>= 1; } return buffer; } Добавлено Я имел ввиду буфер, который не создается в динамической памяти Добавлено Цитата Qraizer @ Формально malloc() может вернуть NULL. Подозреваю, что ему не нравится тот факт, что нет на это поверки Добавлено P.S. Вторая функция в целом плохо спроектирована. Во-первых, динамический буфер от malloc() должен освобождаться. Помимо того, что я нигде не вижу free(), а увидеть я его мог бы только на вызывающей стороно, т.е. в данном случае в main(), так ещё и вызываемому коду неоткуда взять параметр для free(), т.к. ей приходит не тот же указатель, что вернула malloc(), а насколько-то там увеличенный. А как правильно это делается? Надо передавать буфер и параметры, созданные вызывающей стороной? Чтобы она их и освободила? |
![]() |
Сообщ.
#7
,
|
|
Цитата tumanovalex @ Естественно неправильно. Тут buffer[] является локальным автоматическим объектом, чьё время жизни заканчивается на return, т.ч. возвращаемый указатель указывает в никуда.Функции работают нормально. Но хотелось бы сделать так, чтобы в функцию передавать только source, размер типа и буфер создавать в функции и иметь возможность содержимое буфера передавать вызывающей стороне, что-то типа такого: ... В таком виде printf("TypeToBin3 a = %s\n", TypeToBin3(a)) не возвращает правильное содержимое буфера. Подскажите, пожалуйста, как правильно вернуть содержимое буфера, созданного в функции (если это вообще возможно) На предмет того, чтобы сделать всё правильно, вариантов уйма, и все в той или иной степени подойдут или нет в зависимости от предпочтений. Основная идея – у буфера должен быть владелец. Вылечить TypeToBin2() несложно, нужно лишь возвращать результат malloc(), а не какой-то другой, и тогда вызывающий код легко сделает free(), когда ему будет удобно. Это означает, что TypeToBin2() придётся переместить результат к началу буфера, чтобы впереди не было мусора. Но если уж говорить за третий вариант, то у нас уже есть готовые владельцы, например std::vector<> или std::string, так что своего ничего писать не обязательно. Например: ![]() ![]() template <class T> std::string TypeToBin3(T source) { int const size = 8 * sizeof(T); std::string p(size, ' '); for (int i = size - 1; i >= 0; i--) { p[i] = (source & 1) + '0'; source >>= 1; } return p; } |
Сообщ.
#8
,
|
|
|
Спасибо большое за подробные объяснения и код!
|
Сообщ.
#9
,
|
|
|
Дополнил код считыванием и установкой битов:
![]() ![]() #include <string> template <class T> std::string TypeToBin(T source) { int const size = 8 * sizeof(T); std::string str(size, ' '); for (int i = size - 1; i >= 0; i--) { str[i] = (source & 1) + '0'; source >>= 1; } return str; } template <class T> int GetBit(T source, int nBitNumber) { int mask = 1 << nBitNumber; source = mask & source; if(source) return 1; else return 0; } template <class T> T SetBit(T source, int nBit, int nBitValue) { int mask = 1 << nBit; if (nBitValue == 0) { mask = ~mask; source = source & mask; } else { source = source | mask; } return source; } int main() { //char a = 'a'; //int a = 257; short a = -257; int nBit = 15; int nBitValue = 0; printf("GetBit%d = %d\n", nBit, GetBit(a, nBit)); printf("TypeToBin a = %s\n", TypeToBin(a).c_str()); short res = SetBit(a, nBit, nBitValue); printf("res = %d\n", res); printf("GetBit%d = %d\n", nBit, GetBit(res, nBit)); printf("TypeToBin a2 = %s\n", TypeToBin(res).c_str()); system("pause"); return EXIT_SUCCESS; Прикреплённый файл ![]() |
Сообщ.
#10
,
|
|
|
Цитата tumanovalex @ Возник вопрос: можно ли сделать так, чтобы переменная res в main, которой присваивается результат SetBit, зависела от типа первого параметра этой функции? Можно. Вот так ![]() ![]() int main() { //char a = 'a'; //int a = 257; short res, a = -257; int nBit = 15; int nBitValue = 0; printf("GetBit%d = %d\n", nBit, GetBit(a, nBit)); printf("TypeToBin a = %s\n", TypeToBin(a).c_str()); res = SetBit(a, nBit, nBitValue); printf("res = %d\n", res); printf("GetBit%d = %d\n", nBit, GetBit(res, nBit)); printf("TypeToBin a2 = %s\n", TypeToBin(res).c_str()); system("pause"); return EXIT_SUCCESS; } |
Сообщ.
#11
,
|
|
|
Спасибо! Как я сам не сообразил!
|
![]() |
Сообщ.
#12
,
|
|
Цитата tumanovalex @ Работает – и ладно. Я б не так писал. Например, SetBit() используется для установки бита в указанное значение, однако мы привыкли, что установка бита – это конкретно в 1, а если в 0, то это сброс бита. Поэтому интуитивнее видеть не одну функцию, а две setBit() и clearBit() и без указания значения, и так понятно, для какой какое. Или вот GetBit() формально возвращает значение бита, поэтому 0 или 1 понятно. Но мы привыкли, что бит либо взведён либо сброшен, поэтому вместо получения значения и сравнения с чем-то там, просто спрашиваем, взведён ли, и в ответ получаем bool. Но это на любителя.Буду благодарен за любые замечания и предложения Но вот что не на любителя... попробуй передать отрицательный номер бита или с большим значением, например, 57. По Стандарту это неопределённое поведение, так что результаты могут удивить. Или вот смешивание C и C++. Зачем printf(), если есть std::cout? А недавно нам Python-овый format() завезли, так вааще лафа. Цитата tumanovalex @ std::any. Некое подобие динамической типизации. Не совсем, но очень похоже: в него можно положить всё, и он помнит, какой тип в него положен, однако при чтении нужно самому знать, что там. Нельзя положить, скажем int, а вытащить строку, бросит эксепшн. Ниже примерный код, основанный на decltype. Тоже очень удобная вещь, но статическая, в динамике тип не определяет.Возник вопрос: можно ли сделать так, чтобы переменная res в main, которой присваивается результат SetBit, зависела от типа первого параметра этой функции? ![]() ![]() int main() { char b = 'a'; int c = 257; short a = -257; int nBit1 = 15; int nBit2 = 0; int nBitValue1 = 0; int nBitValue2 = 1; printf("GetBit%d = %d\n", nBit1, GetBit(a, nBit1)); printf("TypeToBin a = %s\n", TypeToBin(a).c_str()); std::any res = SetBit(a, nBit1, nBitValue1); printf("res = %d\n", std::any_cast<decltype(a)>(res)); printf("GetBit%d = %d\n", nBit1, GetBit(std::any_cast<decltype(a)>(res), nBit1)); printf("TypeToBin a2 = %s\n", TypeToBin(std::any_cast<decltype(a)>(res)).c_str()); printf("GetBit%d = %d\n", nBit2, GetBit(b, nBit2)); printf("TypeToBin b = %s\n", TypeToBin(b).c_str()); res = SetBit(b, nBit2, nBitValue1); printf("res = %d\n", std::any_cast<decltype(b)>(res)); printf("GetBit%d = %d\n", nBit2, GetBit(std::any_cast<decltype(b)>(res), nBit2)); printf("TypeToBin b2 = %s\n", TypeToBin(std::any_cast<decltype(b)>(res)).c_str()); printf("GetBit%d = %d\n", nBit1, GetBit(c, nBit1)); printf("TypeToBin c = %s\n", TypeToBin(c).c_str()); res = SetBit(c, nBit1, nBitValue2); printf("res = %d\n", std::any_cast<decltype(c)>(res)); printf("GetBit%d = %d\n", nBit1, GetBit(std::any_cast<decltype(c)>(res), nBit1)); printf("TypeToBin c2 = %s\n", TypeToBin(std::any_cast<decltype(c)>(res)).c_str()); system("pause"); return EXIT_SUCCESS; } |
Сообщ.
#13
,
|
|
|
Цитата tumanovalex @ можно ли сделать так, чтобы переменная res в main, которой присваивается результат SetBit, зависела от типа первого параметра этой функции? Есть вариант, как если вместо std::any использовать auto. Вот переписанный вариант от Qraizer: ![]() ![]() int main() { char b = 'a'; int c = 257; short a = -257; int nBit1 = 15; int nBit2 = 0; int nBitValue1 = 0; int nBitValue2 = 1; printf("GetBit%d = %d\n", nBit1, GetBit(a, nBit1)); printf("TypeToBin a = %s\n", TypeToBin(a).c_str()); auto res1 = SetBit(a, nBit1, nBitValue1); printf("res = %d\n", res1); printf("GetBit%d = %d\n", nBit1, GetBit(res1, nBit1)); printf("TypeToBin a2 = %s\n", TypeToBin(res1).c_str()); printf("GetBit%d = %d\n", nBit2, GetBit(b, nBit2)); printf("TypeToBin b = %s\n", TypeToBin(b).c_str()); auto res2 = SetBit(b, nBit2, nBitValue1); printf("res = %d\n", res2); printf("GetBit%d = %d\n", nBit2, GetBit(res2, nBit2)); printf("TypeToBin b2 = %s\n", TypeToBin(res2).c_str()); printf("GetBit%d = %d\n", nBit1, GetBit(c, nBit1)); printf("TypeToBin c = %s\n", TypeToBin(c).c_str()); auto res3 = SetBit(c, nBit1, nBitValue2); printf("res = %d\n", res3); printf("GetBit%d = %d\n", nBit1, GetBit(res3, nBit1)); printf("TypeToBin c2 = %s\n", TypeToBin(res3).c_str()); system("pause"); return EXIT_SUCCESS; } Преимущество: auto - явно даёт более "легкий" код по сравнению с std::any. Недостаток: если переменную, объявленную как auto, "переиспользовать", то можно ожидать сюрпризы, т.к. она получает тип только в момент объявления, а std::any помнит тип и в процессе переинициализации. Поэтому в своем примере я вынужден был объявлять три разных переменных, и переинициализацию не использовать. Ну и вдогоночку замечания, почему так работу с битами лучше не делать: 1) При последовательном вызове SetBit, GetBit, TypeOfBin очень легко можно перепутать имена переменных, и очень часто это не приведёт к ошибке времени компиляции. А при выполнении будут ошибочные данные. Для этого и придумали инкапсуляцию от ООП. И она часто помогает в таких случаях. Оформляй это классом, а функции оформляй методами. 2) Установка, чтение и печать битов более-менее подойдет только для POD-типов. А вот представь, что ты в качестве источника используешь указатель, к примеру, на 64кб массив char. Хана твоей логике. Для того, чтобы это не случилось - тебе нужно написать метод побайтного доступа к той переменной, с которой будет работать твой класс. Подчеркиваю, только метод доступа - содержимое самой этой переменной дублировать не следует. 3) Индексы установки/чтения битов в методах нужно сразу проверять на выходы за допустимые пределы, иначе будет лабуда! |
Сообщ.
#14
,
|
|
|
ADD:
tumanovalex, если в качестве теста/обучения - нет проблем, экспериментируй. Но если хочется чего-то более предметного, что можно включать в свои проекты - то лучше не изобретать велосипед, а воспользоваться уже готовыми решениями из STL. Вот небольшой примерчик (ИСПРАВЛЕНО): ![]() ![]() #include <iostream> #include <bitset> #include <array> int main() { // Создаем массив char размером 16 и инициализируем его std::array<unsigned char, 16> byteArray = {0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x01}; // Создаем bitset, используя массив std::bitset<128> bits; for (size_t i = 0; i < byteArray.size(); ++i) { bits <<= 8; bits |= byteArray[i]; } // Устанавливаем 3-й и предпредпоследний биты bits.set(2); bits.set(125); // Выводим результат std::cout << bits << std::endl; return 0; |
Сообщ.
#15
,
|
|
|
Спасибо, Qraiser и Majestio, за объяснения и код!
|
![]() |
Сообщ.
#16
,
|
|
Majestio, ты что-то путаешь. У std::bitset<> нет конструктора, принимающего массив байт. Его конструкторы, принимающие строки в том или ином виде, как обозначение бита рассматривают каждый символ, а не бит. Т.е. можно получить std::bitset<8>("00110110").to_ulong() == 0b110110ul (или std::bitset<8>(0b110110ul).to_string() == "00110110"), но не или std::bitset<8>("01").to_ulong() == 0b00000001ul
|
Сообщ.
#17
,
|
|
|
Qraizer, да - всё верно! Перепутал я малёха. Свой пример с std::bitset я исправил.
|
Сообщ.
#18
,
|
|
|
tumanovalex, кстати, я тоже когда-то делал для себя заготовки для работы с битами без STL - ознакомься
![]() |
![]() |
Сообщ.
#19
,
|
|
В общем, если кому интересно, то универсальный код преобразования обоих типов ...э-э-э, типов в битовый вид.
![]() ![]() #include <limits> #include <concepts> #include <cmath> #include <bit> /* преобразование целых в строку */ std::string TypeToBin(std::integral auto source) { int const size = std::numeric_limits<unsigned char>::digits * sizeof(source); // размер в битах std::string str(size, ' '); for (int i = size - 1; i >= 0; i--) { str[i] = (source & 1) + '0'; source >>= 1; } return str; } /* преобразование вещественных в строку (для не IEEE-совместимых не тестировалось) */ std::string TypeToBin(std::floating_point auto source) { std::string buf; int power; // порядок source const auto &properties = std::numeric_limits<decltype(source)>{}; const unsigned shiftedPower = static_cast<unsigned>(properties.max_exponent - properties.min_exponent); buf.reserve(properties.digits + std::bit_width(shiftedPower) + 3); // + точка, 'b' и знак buf = std::signbit(source) + '0'; // первым знак if (!std::isfinite(source)) // проверка на спец.форматы NaN и inf { // сначала избавиться от порядка, но сохранить мантиссу (на случай NaN особенно) // используем бесконечность как образец в памяти ... decltype(source) pattern = properties.infinity(); const char (&nanByte)[sizeof(pattern)] = reinterpret_cast<const char (&)[sizeof(pattern)]>(pattern); char (&srcByte)[sizeof(source)] = reinterpret_cast< char (&)[sizeof(source)]> (source); // ... и обрабатываем source побайтно, стирая порядок ... for (int i = 0; i < sizeof(nanByte); ++i) srcByte[i] ^= nanByte[i]; // ... а затем заменяем его на несмещённый порядок 0 // это также учтёт явный старший бит, если он есть, т.к. XOR применяется дважды // + нам пофик на endianess, т.к. он одинаковый для source и pattern pattern = 1; for (int i = 0; i < sizeof(nanByte); ++i) srcByte[i] ^= nanByte[i]; source = abs(source); // теперь можно вычислять модуль, в source не спец.формат // максимальный порядок спец.форматов с учётом его разрядности в типе source, т.к. реализации // любят в FP_ILOGBNAN совать просто MAX_INT power = FP_ILOGBNAN & (std::bit_ceil(shiftedPower) - 1); } else // для остальных форматов всё значительно проще { source = abs(source); // модуль можно сразу power = logb(source); // порядок source= ldexp(source, -power); // чистая мантисса (с порядком 0) // использовать frexp() нежелательно, т.к. она возвращает мантиссу, сдвинутую в [0.5, 1) // что уменьшает точность на бит и требует специальной дополнительной обработки её и порядка, // особенно рядом с std::numeric_limits<>::denorm_min() } /* обработать мантиссу */ for (int i = 0; i < properties.digits; ++i) { buf += static_cast<char>(static_cast<int>(source)); source -= buf.back(); buf.back()+= '0'; source *= static_cast<decltype(source)>(2); } buf.insert(2, 1, '.'); // добавить точку в buf // обработать порядок buf += 'b'; // power хранит меньшую разрядность, чем способен его тип, например 12 для IEEEшного double, // поэтому имеет смысл отрезать от него только нужное количество младших бит buf += TypeToBin(power).substr(std::numeric_limits<decltype(power)>::digits - 1 // знак уже учтён - std::bit_width(shiftedPower / 2)); // а порядок смещён return buf; } |