Бага в классе String в IDE версии 1.6.7
- Войдите на сайт для отправки комментариев
Приветствую!
Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую. Заметил, что ВНЕЗАПНО перестал работать отсыл СМС. Подумал, поковырял, почесял репу - вроде код валидный, но - не работает. Откатил изменения - работает. А теперь - розыгрыш призов с выдачей свекольного ордена разработчикам класса String!
Вот так: работает:
String out; out.reserve(2); char ch1, ch2; out = String(ch1); out += String(ch2);
А вот так - не работает:
String out; out.reserve(2); char ch1, ch2; out[0] = ch1; out[1] = ch2;
Обратите внимание на вызов reserve - это чтобы не дёргать постоянно память. Начал разбираться, почему при прямом доступе по индексу символов - не работает, нашёл вот такую любопытную шляпу:
char & String::operator[](unsigned int index) { static char dummy_writable_char; if (index >= len || !buffer) { dummy_writable_char = 0; return dummy_writable_char; } return buffer[index];
Обратите внимание на условие index >= len. Буфер-то выделен вызовом reserve, длина строки при этом - 0 (len == 0). И тут я ВНЕЗАПНО хочу записать в нулевую ячейку какой-то байт. И - обломинго, бро, иди кури бамбук.
Налицо - архитектурный косяк. Мне плевать, что там лежит в len, главное чтобы переданный индекс вмещался в capacity - 1. Да, при таком подходе код этого оператора сильно усложняется, но оно меня парит вообще? Есть буфер, есть индекс - чего бы не записать туда символ-то?
Как считаете - косяк или нет? Я - за косячище, и оправдания типа "если записать один символ по центру пустого буфера - чего тогда будет?" - не катят: это не дело класса String, строить предположения, чего там будет - на крайняк - всегда есть нуль-терминатор в буфере, а кто запихал по центру мусора один символ и потом получает хз что - ССЗБ.
Собственно, мелочь, а крови попила :)
оптимизирую твою личную проблему:
Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую.
Собственно, мелочь, а крови попила :)
оптимизирую твою личную проблему:
Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую.
Собственно, мелочь, а крови попила :)
Спасибо, но думаю, лучше со старыми граблями, чем ловить россыпь новых - там их, судя по всему, не убавилось, а только прибавилось :)
Спасибо, но думаю, лучше со старыми граблями, чем ловить россыпь новых - там их, судя по всему, не убавилось, а только прибавилось :)
за что "спасибо" - проблема то не решена.
скачай в зипе последний актуальный релиз ИДЕ - протестируй, присутвует ли проблема.
если отсутствует, но ты не желаешь новых проблем, то:
сравни файлы WString.cpp, WString.h со своего актуального ядра и с последнего актуального релиза - посмотри, чем отличаются, замени, протестируй.
Так получилось, что сижу под 1.6.7
В 1.6.8 и в 1.6.9 ровно та же хрень.
Как считаете - косяк или нет?
Как говорят наши американские партнёры, "it depends". Если писал я, то это фича, а если кто другой, то, конечно же баг :)
Вообще-то String - это объект, ни разу не массив символов. И как ему хранить эти символы внутри себя - его личное дело. Поэтому обращение к String как к массиву, на мой взгляд, вообще не должно компилироваться.
Вообще-то String - это объект, ни разу не массив символов. И как ему хранить эти символы внутри себя - его личное дело. Поэтому обращение к String как к массиву, на мой взгляд, вообще не должно компилироваться.
Вообще-то, у класса String определён ссылочный оператор доступа по индексу, его код я и привёл выше. Если определили оператор - то будьте добры обеспечить его нормальную работу. То, что я привёл - это бага.
З.Ы. Если не очень разбираетесь в ООП - так и скажите, вместо того, чтобы писать, как бы помягче это сказать, недостоверные домыслы.
Если хотите моё мнение - да, String это объект, и его основное свойство - это длина, len.
Все операции со строкой, по-моему, вполне логично выполнять в пределах её длины - в том числе, обращение по индексу.
А вот свойство capacity, скорее, является чем-то дополнительным и неестественным. По логике вещей, программисту для работы со строкой оно не нужно - всё что можно сделать со строкой, легко делается без capacity.
Вот только делается, иногда, очень медленно. Поэтому и пришлось прилепить сбоку это дополнение - для увеличения скорости, но никак не для изменения логики работы с объектом.
Так что, извините - мой голос за "не баг". :)
Так что, извините - мой голос за "не баг". :)
Исходники класса String смотрели хоть? Они же кривые все, как моя жизнь. Не баг, говорите? Ок, по вашему, не баг. Допустим, у нас в строке уже будет что-то записано, например, "Hello". Какая длина строки, сиречь, len? 5 (возможно, мультибайтовых, заметьте) символов.
И вот я беру, и пишу helloString[2] = '\0';
Какая длина строки-то теперь? Что там прилеплено сбоку - capacity или такие len? Там криво всё, как курятник бабушки Нюры.
Если честно - нет, не смотрел. Может и кривые - я не спорю. Просто написал, как мне самому было бы понятней - исходя из обычной человечьей логики. И даже извинился там, на всякий случай.
Насчёт helloString[2] = '\0'; смотреть не полезу, но насколько я понимаю, на len это никак не повлияет?
Если так, то я с этим тоже вполне согласен, как ни странно. Длина String'а, по моим представлениям, определяется именно значением len, а не содержимым строки. А символ '\0', по-моему, ничем не лучше и не хуже любого другого символа.
-------
Насчёт мультибайтовых символов озадачился...
Неужели в ардуиновском String'е действительно предусмотрена работа с мультибайтами? Не верится, если честно - но проверять не полезу, лениво. Соответственно, утверждать тоже ничего не буду. Лучше ещё раз извинюсь. :)
Насчёт мультибайтовых символов озадачился...
Неужели в ардуиновском String'е действительно предусмотрена работа с мультибайтами? Не верится, если честно - но проверять не полезу, лениво. Соответственно, утверждать тоже ничего не буду. Лучше ещё раз извинюсь. :)
Именно так. Если в настройках стоит substitute_unicode=true, то все строки кодируются в UTF-8, т.е. String вполне себе их переваривает. Представляете, какой секас будет в этом случае с оператором доступа по индексу? Именно поэтому я утверждаю, что класс писан очень криво.
По поводу символа '\0', смотрите, какая шляпа: делать предположение о длине хранящейся в буфере строки только на основании переменной len - в корне, именно в корне - неправильно. Давайте для простоты разберём вот такой случай, считая, что строка - в однобайтовой кодировке:
А теперь представьте, что на основании информации, возвращаемой методом length() мы пишем в пакет заголовок "длина строки - такая-то", потом - данные строки, а на принимающей стороне ловим эти байты. И тут, внезапно - по центру строку нуль-терминатор, по итогу - строка не та, которая ожидалась. Я уже не говорю про мультибайтовые кодировки ;)
Т.е. в классе String крайне неудачно пытались скрестить ужа с ежом, а именно - байтовый буфер и собственно класс - держатель строк в различных кодировках. Вы же согласитесь с тем, что нуль-терминатор в строке - он и в Африке нуль-терминатор? Даже в случае однобайтовой кодировки реализация класса String - ущербна, вот я о чём.
Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.
Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.
Ну я бы не был так категоричен, конечно. Он вполне себе удобен как менеджер динамического буфера, например. Скажем, я его вполне себе юзаю для работы с UTF-8 и СМС на русском языке, да и во многих других местах, где его применение оправдано и удобно. Просто обратил внимание общественности на несколько неоднозначную реализацию одного оператора класса, вот и всё.
И тут, внезапно - по центру строку нуль-терминатор
предлагаю не мелочиться, а довести ситуацию до полного абсурда - строить строку исключительно из символов '/0' и утверждать, что у нас нихрена не работает класс стринг.
ведь, не работает же!
Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.
верно - член стеклянный не выдавать.
предлагаю не мелочиться, а довести ситуацию до полного абсурда - строить строку исключительно из символов '/0' и утверждать, что у нас нихрена не работает класс стринг.
ведь, не работает же!
Ну вот опять ты передёргиваешь :) Ты прекрасно понял, о чём я, уверен на 100%. И кривая реализация char& operator[] у класса String - твоё передёргивание не поддерживает ;)
Ок, скажу по-другому: длина строки должна считаться исключительно по нуль-терминатору, и если нам дают записать его по центру строки - длина должна пересчитываться, как минимум.
Ок, скажу по-другому: длина строки должна считаться исключительно по нуль-терминатору, и если нам дают записать его по центру строки - длина должна пересчитываться, как минимум.
длина какого из двух кусков хакнутой на две части строки?
я не сильно разбираюсь, но что увидел в глючном коде ТС - ровно то же, на чём вначале споткнулся сам.
имеем:
думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.
если уж заюзали String, то что-то делаем с ним этим
длина какого из двух кусков хакнутой на две части строки?
Очевидно, что от начала буфера, куда ссылается внутренний указатель.
думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.
Ты наступаешь на неправильные грабли ;) Смотри: оператор класса - это, по сути, та же самая функция-метод класса, список которых ты так любезно привёл. И если у класса определён ссылочный оператор доступа по индексу (char& operator[]) - то я, как программист, пользующийся этим классом, считаю, что поведение этого оператора будет адекватным, при этом не зная, как всё это реализовано внутри (инкапсуляция, паанимаишь).
И вот смотри, я начинаю манипулировать со строкой:
Я ожидаю, что методы будут работать адекватно, а не как бог на дущу положит. Продолжая пример: у класса String есть замечательный метод c_str() который выдаёт нам константный указатель на внутренний буфер. Для ясности кусочек кода:
Помедитируй над этим примером и пойми, что сочетание того, что наколбашено в классе String - дичь дикая. По сравнению с теми имплементациями класса String, которые я последние лет 15 юзаю, начиная от MFC и заканчивая C#.
Впрочем, разговор уже повернул в идеологическую сторону. Для себя я сделал только один вывод: не пользоваться криво написанным ссылочным оператором доступа по индексу у класса String, вот и всё. Ибо там реально - кривота.
Кстати, вдогонку: емнип именно по причинам неоднозначности в STL нетути
Для класса std::string. Щас пойду гляну импрементацию, на всякий.
З.Ы. Поглядел имплементацию SGI - точно, нету, и неспроста нету.
З.З.Ы. Думаю, тему можно считать закрытой, мне уже чего-то неинтересно :)
думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.
И вот смотри, я начинаю манипулировать со строкой:
снова за рыбу деньги
или String - объект класс, тогда
или string - массив символов, тогда
в случае
что-то может пойти не так, но как это может случиться, если ты адекватен и юзаешь верхние два варианта?
при длинне буфера 7 символов что по вашему должен вернуть оператор [111] ???
по моему было-бы правильно вернуть null, но тогда код грозит получить не диагностируюмую рунтайм, решение которое в топике - согласен, оно не корректное с точки зрения понимания но оно безопасное, вы никогда не запишите муть поверх других объектов...
с другой стороны это свойство введено для поддержки Print и прочего связаного с выводом в поток.
по этому считаю, решение разработчиков "не красивым" но по сути верным, оно безопасное и поддерживает желаемый функционал потоковых классов.
надеюсь я объяснил почему именно так сделано?
зы
можно было сделать при попытке залезть за границу автоматическое выделение памяти и расширение массива буфера, но такое поведение несет куда больше проблемм, по тому как неявное выделение памяти - для систем подобных ардуино - одна из самых сложнодиагностируемых ошибок.
Клапауцию: запись str[2] не означает, что я обращаюсь к классу как к массиву символов, это ключевой момент. Пока ты это не поймёшь - мы говорим на разных языках.
vde69: очевидно, по стандарту должен бросать исключение при доступе за пределами выделенного буфера. Но, поскольку мы на МК - вполне достаточно вернуть ссылку на статический символ, который можно хоть до усёру изменять (что, собственно, и сделано в классе String).
Примеры кривости класса String я привёл, считаю, что это именно кривость, и решение разработчиков обеспечить поддержку
char& operator[]
в классе String - криворуким. Уж лучше бы они не делали этот метод, как и поступили гораздо более умные ребята из SGI, выпуская свою версию STL.
Всё, для себя тему свернул, всем спасибо за участие.
Клапауцию: запись str[2] не означает, что я обращаюсь к классу как к массиву символов, это ключевой момент. Пока ты это не поймёшь - мы говорим на разных языках.
на настоящую реальность никак не влияет, что я считаю.
ты так и не ответил, корректно ли у тебя работает
если "да", то зачем ты используешь нерабочую конструкцию?
ты так и не ответил, корректно ли у тебя работает
если "да", то зачем ты используешь нерабочую конструкцию?
Вот кусок кода из исходников:
Я ответил на твой вопрос? Если нет, то поясню - работает некорректно и в этом случае.
ты так и не ответил, корректно ли у тебя работает
Я ответил на твой вопрос? Если нет, то поясню - работает некорректно и в этом случае.
нет - не ответил. ты же давно должен понять, что я не программист - я мастер по ремонту бытовой техники.
поэтому: как ты диагностируешь неполадку, как проявляется неполадка, пр.
fix
народ, колитесь - где я пробегаю между грабель.
fix
Мастер по ремонту бытовой техники понимает разницу между символом '\0' и символом '0'? Если нет - тогда и говорить не о чем.
Мастер по ремонту бытовой техники понимает разницу между символом '\0' и символом '0'?
понимаю.
Если нет - тогда и говорить не о чем.
тогда есть, что говорить программисту - нафига программист хакает строку и возумащается, что ему за хак не оторвало руки?
кароче - зачем ты используешь символ, предназначенный для обозначения конца строки не по назначению?
Ок, последняя итерация: если какой-то метод разрещает устанавливать ЛЮБОЙ символ, включая нуль-терминатор, в произвольную позицию, то я, по крайней мере, жду, что в случае передачи нуль-терминатора длина результирующей строки пересчитается (это ведь очень просто, на самом-то деле). Если этого не делается - это баг.
Давай уже закончим: я уже понял, что тебе нужен спор ради спора, не более. Я открыл топик не для спора, а для того лишь, чтобы читающие могли ознакомится с недоработками встроенного класса String. Я не призываю хакать строки, как ты выразился, я писал лишь об архитектурной неполноте класса. Мне, как программисту, эта неполнота очевидна в полной мере. Как другим - это уже не моё дело.
Ок, последняя итерация: если какой-то метод разрещает устанавливать ЛЮБОЙ символ, включая нуль-терминатор, в произвольную позицию, то я, по крайней мере, жду, что в случае передачи нуль-терминатора длина результирующей строки пересчитается (это ведь очень просто, на самом-то деле). Если этого не делается - это баг.
В целом по теме больше согласен с Вами, чем нет, но вот здесь Вы хватанули через край.
Нуль-терминатор тут вообзе не при делах. Он имеет смысл терминатора в последовательности символов, чтобы определить конец (длину строки). В классе String он не несёт нагрузки терминатора. Длина там определяется по-другому и экземпляр класса String может состоять хоть из ста нулей - имеет право.
Давай уже закончим:
нет - не закончим: я хочу воспроизвести, описанный тобой баг методами String, а не string
смотри - у меня не получается:
Нуль-терминатор тут вообзе не при делах. Он имеет смысл терминатора в последовательности символов, чтобы определить конец (длину строки). В классе String он не несёт нагрузки терминатора. Длина там определяется по-другому и экземпляр класса String может состоять хоть из ста нулей - имеет право.
Согласен и с этим. Но! Это ещё раз подтверждает курсы кройки и шитья, с помощью которых свёрстан класс String. Длина там лежит в переменной len, ок. Тогда объясните мне, как можно было так придумать, что вот это - не работает по-любому:
Право имею? Буфер зарезервировал, оно есть где-то внутри там, может состоять, как вы выразились, хоть из ста нулей. len при этом - 0, и вызов setCharAt (чтобы не ломать картину мироздания Клапауция - я уж по простому, без операторов доступа по индексу) - обламывается.
Где написано, что я обязан что-то записать в строку до того, как обращаться по этому индексу? Если мы оперируем понятиями буфера? В таком случае получается, что имплементация reserve выполнена косячно, раз String может держать в себе совершенно любые символы. Или, что вернее - что-то пришито сбоку бантиком: начали с нормальной имплементации простого строкового класса, а затем сбоку припиздячили полочку, которая то и дело норовит отвалится.
Особенно умиляет, при таком раскладе, наличие методов типа getBytes - типа, байты с внутреннего буфера я получить могу любые, а вот зарезервировать память, потом записать туда любой байт - обломись, вражина. Так, что ли?
Вы не находите нигде противоречия?
Давай уже закончим:
нет - не закончим: я хочу воспроизвести, описанный тобой баг методами String, а не string
смотри - у меня не получается:
Поправил твой код, скомпилируй и насладись.
Поправил твой код, скомпилируй и насладись.
а, в чём суть? - два непечатных символа NUL в строке
что бы было интерактивно кнопкой, то так:
А теперь попробуй так, для полного фаршу:
А потом представь, что потребности в классе String не ограничиваются только его методами, а есть случаи, когда нужно работать с указателем на строку (метод такой нам класс String предоставляет? Предоставляет.). И передать вовне длину строки, а затем - саму строку:
Али прикажешь в таком случае не надеяться на метод length(), а тупо юзать strlen()? Знаю, выход, выход. Знаю, нельзя так изгаляться над бедным классом String - не мной писан, а я, гад, на святое руку поднял. Всё знаю.
Ты прав - в классе String нет багов, признаю это. Каюсь, посыпаю голову пеплом, позорно умолкаю и бреду в угол, вставать на чечевицу.
Ты прав - в классе String нет багов, признаю это. Каюсь, посыпаю голову пеплом, позорно умолкаю и бреду в угол, вставать на чечевицу.
да, шож такое - это какой-то BDSM, а не конструктивный диалог.
я сломал игрушку - игрушка плохая, вы все бяки. ко-ко-ко.
да, шож такое - это какой-то BDSM, а не конструктивный диалог.
я сломал игрушку - игрушка плохая, вы все бяки. ко-ко-ко.
Ахахах, спасибо, поржал :) Да просто мне поднадоело сухой теорией развлекаться, вот и всё. Понятное дело, что на практике я не сказочный долбоёб, который только и делает, что нули пихает куда ни попадя. Однако - в жопе свербит от вселенской несправедливости, вот и кручусь как могу :)
Ахахах, спасибо, поржал :) Да просто мне поднадоело сухой теорией развлекаться, вот и всё. Понятное дело, что на практике я не сказочный долбоёб, который только и делает, что нули пихает куда ни попадя. Однако - в жопе свербит от вселенской несправедливости, вот и кручусь как могу :)
ну, и ладно.
Вы не находите нигде противоречия?
Ну, на эту тему я уж почти неделю назад высказался.
Попкрон вечен
Попкрон вечен
Хрен там! Кончается, сцуко, в самый неподходящий момент.