LINUX.ORG.RU

Правильный(???) стиль работы со строковыми данными на С

 ,


2

4

Иногда (прямо сейчас) приходится обрабатыавть строки на C.
Меня коробит от громоздкости операций выделения памяти, конкатенации и самое главное это snprintf с проблемой размера буфера под конечную строку, гигантское поле для выращивания вских мемориликов по невнимательности.

К примеру, размеры mult1_str, mult2_str и equal_str известны, нужно выделить память под всю строку:

snprintf(
    buf,
    buflen,
    "%s miltilple %s equals %s",
    mult1_str,
    mult2_str,
    equal_str
    );
варианты:
- махнуть шашкой и сделать килобайт на стеке ( ((( )
- ничем не размахивать и посчитать руками. (еще хуже)
- написать функцию которая будет вычислять длину «%s miltilple %s equals %s» без символов подстановки (уже лучше)
- отказаться от snprintf и собирать строку пачкой конкатенаций с аллокациями памяти и прочим...

А как делаешь ты?


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

Да нет - это ты мне ответь: где, по-твоему, хранятся значения строчных переменных (объектов) произвольной длины - в стеке или в «куче»?
Не литералов, не указателей - а к примеру значения std::string?

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

Да нет - это ты мне ответь

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

где, по-твоему, хранятся значения строчных переменных (объектов) произвольной длины - в стеке или в «куче»?

Я не просто так спросил - про какую ты реализацию строк? У меня есть реализация где они вообще не хранятся.

Не литералов, не указателей - а к примеру значения std::string?

Ок, std::string. Они могут храниться где угодно. Давай наконец выслушаем твою версию, потом могу рассказать подробности, если ты перестанешь позориться и начнёшь внимать.

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

Ок, std::string. Они могут храниться где угодно.

А где угодно - это где? (без собственного кастомного аллокатора). Вот к примеру, написал ты:

std::string s1 = s2;
где s2 - это тоже std::string переменная с каким-то значением. Что в этот момент происходит внутри STL-класса?

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

Хотя бы понимание того что C++ может всё что может C у вас должно быть?

даже restrict может?

Конечно может

struct T { int i; };
struct T var_1;

int main () {
   T * __restrict  var2 = &var_1;
   int * __restrict  var_3 = &var2->i; // undefined behavior
   {
      int * __restrict  var_4 = &var2->i; // допустимо
   }
   return 0;
}

Проверил и на gcc и на clang и на MSVC

C++ может всё, что может С и плюс ещё много другого.

Но это и хорошо, чтобы знать С++ нужно знать С хотя бы на каком-то уровне. Без С++ было бы меньше знающих С, так как уменьшалась бы мотивация осваивать С...

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

На стеке ...

Не-не-не. Строчка в s2 у нас очень длинная оказалась ) Что в этот момент происходит внутри STL-класса?

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

да, но поддерживаются как минимум тремя самыми популярными компиляторами, может и другими поддерживаются...

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

А где угодно - это где? (без собственного кастомного аллокатора)

О, неужели наконец прочитал про кастомный аллокатор? И сфига ли без него?

Где угодно, это, например, на стеке, потому что SSO. Что позволяет писать высокоуровневый код работающий со строками без вызовов new вообще. С использованием кастомного аллокатора, как ты наконец догадался, данные строк также можно хранить где тебе нравится - в специально выделенном для этого глобальном куске памяти, на стеке, в TLS, в регистрах, где угодно. Так что всю чушь которую ты нёс про необходимость рантайма, якобы предоставляющего некую магическую кучу, непременно написанную на C, можешь забыть. Аллокатор - это всего лишь функция, возвращающая кусок памяти, в зависимости от твоих нужд она может занимать 5 строк и при этом будет работать весь stl.

Далее, std::string - это не единственная реализация строк, и, понятное дело, не самая удобная для embedded программирования, коли мы о нём заговорили. Боюсь разрушить твой мир, но есть реализации строк которые вообще не хранят содержимое строки. Можно хранить дерево, листьями которого являются константы и ссылки на переменные, а узлами - операции конкатенации. Она ничего не аллоцирует, она ленивая, она умеет считать длину строки в compile time (constexpr), при этом взаимозаменяема с std::string + std::to_string, что позволяет один и тот же код (а у нас большая часть кода прошивки рисует менюшки и взаимодействует с пользователем, помимо неё есть только загрузчик и минимальный HAL) собрать как в автоматические тесты (и иметь в итоге 100% покрытие), так и в десктопное приложение эмулирующее девайс, так и в собственно прошивку, изменением одного флага компилятора.

Я хочу чтобы ты как следует подумал как реализоваыл бы всё это на C.

Вот к примеру, написал ты std::string s1 = s2:

Удивительно, но мы опять возвращаемся к вопросу о реализации строк. Потому что в libstdc++ до c++11, когда были позволены refcounted строки, в этот момент происходило только инкремент счётчика ссылок. Не то что ты ожидал?

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

Но тем не менее, это не стандарт, т.е. в c++ его нет. Юзать можно конечно, но потом при сборке под экзотичную платформу оптимизация превращается в тыкву. Впрочем, c99 под те платформы тоже нет, так что в целом пофигу на стандарты.

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

О, неужели наконец прочитал про кастомный аллокатор?

У тебя воспаленная фантазия. Я много лет программировал платежные терминалы на C++ )

«Сфига ли без него» ...

ну так ты ж у нас про прелести высокоуровневого C++ ратуешь, стало быть не должен жизнь усложнять такими сложностями. Чем писать собственную хитрую реализацию хранения строк (еще лишних ошибок наделаешь), может проще и компактнее просто прямо всё контролировать на С? И с чего ты взял, что мы говорим только об «embedded» и все можно посчитать во время «compile time». Ты ж влез в обычную C-шную тему...

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

У тебя воспаленная фантазия. Я много лет программировал платежные терминалы на C++ )

Извини, но с показанными тобой знаниями тебя джуниором никуда не возьмут.

ну так ты ж у нас про прелести высокоуровневого C++ ратуешь, стало быть не должен жизнь усложнять такими сложностями. Чем писать собственную хитрую реализацию хранения строк (еще лишних ошибок наделаешь)

Усложнять? Ты хоть примерно представляешь распределение времени на разработку низкоуровневого кода типа этих строк, которые пишутся и покрываются тестами один раз за полдня, и прикладного который живёт и развивается годами?

может проще и компактнее просто прямо всё контролировать на С?

Не может. Проще и компактнее это так:

temp_message = "temperature="s + to_string(temp) + "°C"s;  // temp - показание с датчика

это одна строка, она очевидна даже девочке-пиэму, здесь негде ошибиться или что-то забыть, здесь нельзя выехать за границу буфера, здесь нельзя взять sizeof или strlen не от той строки, здесь нельзя не освободить ресурсы. При этом она будет работать и на AVR без аллокаций вообще, и на десктопе с изкоробочным stl. Это - высокоуровневый код, и любой код должен быть таким.

На C так сделать нельзя. Ничего из перечисленного. Это не язык программирования, это древность, музейный экспонат.

всё контролировать

Постоянно слышу это от начинающих программистов. Потом это проходит.

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

temp_message = «temperature=„s + to_string(temp) + “°C"s;

Да уж... ради такого, конечно, надо специальный кастомный код писать для хитрого хранения строчек, в котором запросто может оказаться ошибка (тут что C, что С++ - все едино). И не дай бог еще за границу массива вылезешь... )

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

Да уж… ради такого, конечно, надо специальный кастомный код писать для хитрого хранения строчек, в котором запросто может оказаться ошибка

Скажи, ты специально придуриваешься? Кода этих менюшек в одном девайсе - под тысячу sloc, и он постоянно дорабатывается. И ошибка не должна оказаться там, а не в двух страницах реализации строк, написанных 5 лет, покрытых тестами и с тех пор не менявшихся, за исключением добавления to_string для пары дополнительных типов. Я очень хотел бы посмотреть как ты напишешь хотя бы одну десятую этих менюшек на C.

И не дай бог еще за границу массива вылезешь… )

О чём я и говорю. В C всё «не дай бог». В C++ этот класс ошибок также можно полностью исключить, при желании на этапе компиляции.

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

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

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

не дай бог - оказаться в одном рабочем пространстве вот с таким фанатиком

годы разработки менюшек в конторе менюшкошлёпов - у любого крыша съедет

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

И ведь так и не захотел явно признать, что в C++ напрямую встроены динамические запросы памяти. Даже когда уже прямо про new-delete несколько раз напомнили и попросили конкретно объяснить, что происходит при присвоении значений произвольной длины в std::string, ужом стал изворачиваться и вести себя как нашкодивший кошак, которого мордой в лужу тыкают. Сначала попытался смухлевать ссылкой на SSO. Не вышло - на какую-то свою персональную реализацию хранения строк попытался стрелки перевести. Демагог и трепло...

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

slovozap упертый хам, но я бы предпочел работать с ним, у тебя какое-то очень альтернативное восприятие происходящего. Ибо C++ без кучи тебе показали. И, между прочим, new-delete запросто могут не использовать кучу. Короче нет, не «напрямую встроены».

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

И, между прочим, new-delete запросто могут не использовать кучу. Короче нет, не «напрямую встроены».

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

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

Под словом «куча»/heap просто подразумевается вариант постоянного хранения данных и объектов - как альтернатива стеку. Без такого функционала это уже не C++, о преимуществах которого принято твердить. Ну и, естественно, такие альтернативные, самописные «кучи» уж точно не добавляют надежности коду. Си ведь ругают за «ненадежность» - а тут на тебе... «улучшили» C++ )

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

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

Хранить подстроки как интрузивный список, и вроде можно обойтись.

Если использование даже своей кучи недопустимо и в си ты обошелся без нее, то и в C++ обойдешься так же. Контейнеры STL отвалятся большей частью, алгоритмы останутся. И останется еще 100500 фич языка. Не надо думать что он тут же скатится до си.

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

Если использование даже своей кучи недопустимо и в си ты обошелся без нее, то и в C++ обойдешься так же

Т.е. сначала заявляется: С++ такой весь из себя классный и универсальный, а C - «говно» и его нигде использовать нельзя. Ну какой-нибудь молодой студент думает: Во, зашибись - напишу-ка я начальный загрузчик, а потом и новое ядро на C++. Зачем мне этот древний С? И тут начинаются тонкости: этого оказывается нельзя использовать, того тоже нельзя... С runtime-средой непонятки... И вообще чего можно, может рассказать только абстрактный «Вася» - и то, в отношении конкретного компилятора. Зашибись супер-замена.

И останется еще 100500 фич языка.

Ну, это очень сильное преувеличение в отношении системных задач.

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

И тут начинаются тонкости

Есть ещё один момент, который стараются замолчать: на Си можно написать «любой» компилятор. Не на многих «любых» компиляторах можно написать сам компилятор Си.

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

И вообще чего можно, может рассказать только абстрактный «Вася» - и то, в отношении конкретного компилятора

Не эмбеддщик, но открытые ОС на плюсах есть, значит есть база знаний. Целая подгруппа в комитете этим занимается. А с динамической памятью всё довольно ясно и от компилятора не зависит.

это очень сильное преувеличение в отношении системных задач

Навскидку, шаблоны и constexpr много стоят. И вообще язык сильно движется в сторону compile-time возможностей.

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

Несколько раз перечитал, так и не понял. Не только лишь все могут.

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

Лично мне в крестах не нравятся эти попытки заставить ежика летать. Декларативность шаблонов добавляет сложность, которая на мой взгляд нафиг никому не сдалась и растет как попало. Почему нельзя открыть новое направление, где на нормальных классах описали бы весь язык и ими генерировали саму программу? Чтобы долго не объяснять, вот пример:

template<T> T foo(T x) { return x+1; }

Здесь все ровно, но это простой случай. Что если для комплексных чисел нужно добавлять не 1, а 1+i? Или на основании наличия/имени какого-то поля в типе аргумента решать, что делать дальше? Это не движение в компайл-тайм, а просто макросы на стероидах. Метакод сейчас не может общаться с компилятором на равных, он просто имеет совещательный голос. Как насчет:

template<T> void complex_foo(T x) {
    auto m = std::meta(x);
    typedef void T::bar_proto();

    if (m.methods['bar'] && m.methods['bar'].proto == bar_proto) {
        x.bar();
    }

    x.baz();

    auto filename = std::string("./%s/null-members.txt").format(m.identifier);

    for (auto name : read_names_from_file(filename)) {
        m.set_member(name, nullptr); // x.<name> = nullptr
    }

    // прочие прямолинейные подходы
}

И пусть компилятор сам решает, что можно делать в compile-time, а что нет. Контраргументы типа «ну это слишком даст волю стрелять в ногу» уже слышал.

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

Какие, назовите их. Хотя бы понимание того что C++ может всё что может C у вас должно быть?

Трепло, ты ведь ничего не знаешь ни о С ни о С++. Я там другому балаболу задачу дал - иди и ты попытайся её решить.

К тому же, С++ не может ничего сам по себе - т.к. базовый язык(который и может всё) - там си, причём достаточно обрезанный.

При этом современные языки гораздо более выразительны чем убогий сишный

Предоставь основания этим нелепым потугам.

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

Ты не балаболь, а показывай.

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

Это аксиома. Хочешь поспорить - показывай.

Во-вторых, «не имеет механизма динамического выделения-управления памятью» это оксюморон. Если есть память, её можно выделять динамически.

Опять сел в лужу. Я специально проверил и он действительно под динамическим выделением имел ввиду рантайм. Ну вот, задачу я тебе определил - показываешь язык без рантайм-зависимостей/инжектов.

Никакую память ненужно выделять - опять перепутал реальность и свои нелепые представления о матчасти.

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

С++ такой весь из себя классный и универсальный

Твоя информация немного устарела. Актуальный С++ уже частично отвязан от рантайма.

И тут начинаются тонкости: этого оказывается нельзя использовать, того тоже нельзя... С runtime-средой непонятки...

Остались только исключения, но через пару лет(возможно, если опять не пожрут раст-говна) завезут статические исключения.

Ну, это очень сильное преувеличение в отношении системных задач.

В контексте актуального С++ - уже нет. На самом деле нет где-то уже с С++17.

Правда мало кто это всё использует, поэтому у рядовых адептов С++ без рантайма действительно днище.

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

Ты просто написал неведанную херню - ты явно из жабаскрипта сбежал.

По поводу сути твои рассуждений - они имеют смысл. Но ты неверно мыслишь. Т.к. ты адепт жабаскрипта - ты не разделяешь рантайм/компилтайм. В этом твоя проблема.

Если же ты начнёшь их разделять, но пойдёшь дальше и поймёшь текущую проблему С++. И ты, как жабаскриптер, её частично понимаешь.

Дело в том, что декларативность в С++ статичная. Т.е. у нас весь компилтайт - статичный. Нельзя ничего изменять, нельзя ничего переопределять, нельзя ничего удалять. Но.

Ведь по-сути это полная херня. Что нам мешает иметь не статичный, а динамический компилтайм. Ведь комплятор - это попросту интерпретатор конструкций, описанных в коде. А сейчас он ещё и интерпретатор кода.

Но зачем нам язык для интерпретатора ограничивать статикой? Незачем. Поэтому на уровне компилтайма С++ можно превратить в жаваскрипт. Где можно как угодно изменять поля, где можно передавать auto везде и всюду. Не делать никакой разницы между типами и значениями. Сейчас мы живём с костылями вида type_c<T>.

Но. Тут возникает фундаментальная проблема из-за которой все эти данамические «языки» такие примитивные. Никто не может понять как это должно выглядеть и никто не может реализовать подобное.

Допустим, как ты будешь реализовывать асист в ide тогда, когда у тебя пропадёт вся эта декларативность? Там асист до сих пор не может в шаблоны.

Поэтому все и идут по базовому пути. Пихаем что придумали, но ни о чём фундаментальном не думаем. Хотя С++ в этом плане ещё адекватен. Всякие там расты - это просто помойка.

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

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

Хейтеры С - они такие! Мозгов нет, вот и выдумывают всякие отмазки, чтобы С не изучать!!!

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

вместо vla есть alloca (да, нужно быть аккуратнее, так как область действия у alloca функция, а не {} как у vla, и в цикле не нужно делать выделения, но мне и vla в циклах не нужно было)

Вместо compound literals, можно использовать обычные литералы.

char lam[] = "lamerok";
p = lam;

Где нельзя использовать обычные литералы(обычно это всякие макросы), то решаем задачу с помощью ifdef...

Типа такого:

#include <stdio.h>

long double (sum)(long double* arr, size_t arr_size)
{
    long double result = 0.0L;
    for (size_t i = 0; i < arr_size; ++i)
    {
        result += arr[i];
    }
    return result;
}

#ifdef __cplusplus

template<class ... Types> 
long double sum(Types ... args)
{
    const size_t size = sizeof...(Types);
    long double arr[size] = {static_cast<long double>(args)...};
    return sum(arr, size);
}

#else

#define sum(...) (sum)((long double[]){__VA_ARGS__}, \
    sizeof((long double[]){__VA_ARGS__})/sizeof(long double))

#endif


int main()
{
    long double res = sum(1,2,4,5);
    printf("res = %f\n", (double)res);
}

inline есть и в C++.

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

вместо vla есть alloca (да, нужно быть аккуратнее, так как область действия у alloca функция, а не {} как у vla, и в цикле не нужно делать выделения, но мне и vla в циклах не нужно было)

Тебя попросили показать, а ты мне начинаешь рассказывать какие-то истории охренительные. К тому же аллока не имеет никакого отношения к С++.

Вместо compound literals, можно использовать обычные литералы.

Нельзя. То, что ты показал - не имеет никакого отношения к теме. В крестах время жизни - выражение, в сишке - область видимости. Строковые литералы вообще никакого отношения к теме не имеют - у них другая семантика и время жизни.

К тому же - ты не используешь «обычный литерал» - ты используешь его копию. Зачем ты пытаешься меня обмануть?

Где нельзя использовать обычные литералы(обычно это всякие макросы), то решаем задачу с помощью ifdef...

Ещё раз - ты говорил, что может всё. Теперь вместо фичей ты начинаешь плыть и предлагать мне использовать какие-то портянки мусора.

inline есть и в C++.

Нету.

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

Во-первых ты пишешь херню. Во-вторых ты нихрена не понимаешь. То, что ты написал - это не С++. Так же, ты не понимаешь НИЧЕГО. Т.к. твоя портянка вообще никак не связана с проблемой описанной мною.

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

Если серьезно работать с кодировками то не зависимо от библиотеки пути ведут к icu. Иначе будет неадекватное поведение.

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

Владимир

При том, что до кодовых страниц Microsoft UNICODE не было и ее
появление вызвано несовершенством кодовых страниц Microsoft.

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

Владимир

Маленькая подсказка.
С какой кстати в первых 127 байтах всегда находится ASCII?
Первые 127 байт следовало использовать в качестве мета-данных,а символы кодировок в остальных 127 байтах.
При таком подходе для управляющих символы /от 0 до 32/ можно было бы отвести отдельную кодовую страницу.

PS: Выше сказанное - подход /все детали реализации не показаны/.

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

Владимир

Еще раз sorry.
Код каждого символа любой кодировки /не совсем так, но почти/ в
в первых 127 битах содержит мета данные.

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

Владимир

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

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

Владимир

Скорее всего /через некоторое время/ разработаю API для поддержки
выше приведенного способа хранения символов.
В чем профит - длина символа ОДИН БАЙТ.

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

Владимир

Много чего первоочередного ...
Все же реализую library для работы с строками.
От нынешних библиотек - большая печаль /уж поверьте говорю об этом не для того, чтобы что-то «ляпнуть»/.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.