Ответы пользователя по тегу C
  • Почему нельзя применять инкремент к имени массива?

    @Mace_UA
    Потому что это бессмысленно. Как и нельзя применить инкремент к объекту типа std::vector. Неважно, где выделена память -- важно, к какому объекту вы пытаетесь применить эту операцию.
    В случае с массивами инкремент не имеет смысла, т.к. неясно, что должно произойти с самим массивом после этого (ведь инкремент -- операция, модифицирующая свой аргумент, в отличие от, например, оператора +).
    Инкрементить можно итератор/указатель на какой-то элемент этого массива, чтобы получить итератор/указатель на следующий элемент.

    Важно понимать, что массивы не являются указателями. Это принципиально разные сущности.
    Массив содержит в себе все свои элементы и охватывает весь диапазон (нельзя заставить массив уже после создания начать охватывать только часть своего диапазона).
    Указатель же только ссылается на память, принадлежащую кому-то другому (возможно, какому-нибудь массиву).

    Хотя массивы и умеют неявно приводиться к указателям, и во многих случаях это делают автоматически.
    Например, при применении оператора + к массиву.

    Оператор + (в общем случае) не изменяет своих операндов, а создаёт новый объект, поэтому с ним такой неоднозначности не будет.
    В выражении arr+1 (где arr -- массив) компилятор сначала как бы попытается применить оператор + к массиву и целому числу, у него не получится (т.к. к массиву самому по себе что-то прибавлять нельзя -- опять же, это бессмысленная/неоднозначная операция). Но затем он увидит, что у этого массива есть оператор неявной конвертации к чему-то, что можно использовать в таком контексте: указателю. И поэтому воспользуется данным приведением, сконвертирует arr к новому указателю, и применит +1 уже к этому указателю, в результате вернув указатель на следующий элемент.
    Принципиальное отличие в том, что в данном примере сам массив никто не пытается модифицировать.

    Вы можете спросить: почему тогда в случае с ++arr компилятор не провернёт ту же фишку, приведя массив к указателю и применив инкремент к этому указателю?
    Фишка в том, что сам массив от этого не изменится (т.к. менялся бы только временный объект-указатель), и в результате вы бы не получили ничего. Вот совсем ничего. А это вряд ли то, что вы ожидаете. Потому что этот временный проинкрементированный указатель после завершения операции был бы тут же уничтожен -- на оригинальном массиве это бы никак не сказалось. Операция оказалась полностью бессмысленной.

    Поэтому для встроенных типов такие операции над prvalue (временными безымянными объектами, не привязанными к переменной) запрещены. Здесь уже речь не идёт конкретно о массивах.
    Это то же самое, что написать ++(1+2). Не скомпилится. Так как выражение (1+2) вернёт временный объект типа int, не привязанный ни к какой именованной переменной, и его инкрементировать попросту бессмысленно.

    Так что если вы хотите использовать инкремент с массивом, предполагая, что эта операция должна осуществлять переход к следующему элементу -- просто создайте именованную переменную-указатель через описанное выше неявное приведение, и инкрементируйте уже этот указатель.
    В таком случае всё будет в порядке, поскольку 1) данный указатель будет новым объектом, и его изменение само по себе никак не повлияет на сам массив; 2) инкремент указателя -- операция понятная и однозначная:

    int arr[42];
    //++arr; // error!
    int* ptr = arr; // array-to-pointer decay
    ++ptr; // ok!


    То же самое можно сказать и о динамических массивах, не являющихся указателями. В C++ их роль чаще всего играют экземпляры шаблона std::vector:

    std::vector<int> arr(42);
    //++arr; // error!
    int* ptr = arr.data();
    ++ptr; // ok!
    Ответ написан
    Комментировать
  • С или modernC++ для IoT?

    @Mace_UA
    Не думаю, что оверхед от эксепшенов и shared_ptr'ов в C++ намного больше, чем оверхед от аналогичных вещей в Си. Ведь "эксепшены" там можно частично имитировать через setjmp/longjmp и прочие прелести, а "shared_ptr" навелосипедить ручками, сделав структуру для хранения данных, указателя на функцию-делитер и атомарных счётчиков strong и weak ссылок, работа с которыми будет осуществляться через соответствующие функции из <stdatomic.h>.
    Ну и "weak_ptr" заодно заимплементить по аналогии с плюсовым, для полноты картины.

    К чему я это всё?

    Если Вам не нужен определённый функционал -- просто не используйте его.
    И это утверждение справедливо вне зависимости от выбранного языка.
    Если Вам не нужна некоторая часть языка -- это не причина отказываться от него полностью. Весь C++ на сто процентов, по-моему, не нужен вообще ни на одном проекте :)

    А из фишек modern C++ в эмбеддеде наибольшую пользу, скорее всего, принесёт constexpr. В сочетании с шаблонами поможет вынести в компайл-тайм много всего -- причём, в отличие от рекурсивной функциональщины, использующей только шаблоны, это будет смотреться читабельно. В C++11 constexpr слабоват, но начиная с C++14 с ним есть где разогнаться.

    Из структур данных в первую очередь посмотрите на array и tuple.

    Для безопасности могут помочь enum class и nullptr.

    Если в Вашем коде имеют место полиморфные иерархии, то override сделает код понятнее и предотвратит неприятные ошибки с hiding'ом вместо переопределения.

    Рекомендую посмотреть на ютубе лекции Jason'а Turner'а -- у него были видосики о применении современного C++ в эмбеддеде. Он показывал сгенерированный ассемблер -- отличий от аналогичного низкоуровневого сишного кода вообще не было. Зато в исходном коде типобезопасность и расширяемость, которых добиться одной чистой Сишкой не всегда возможно.

    Думаю, с выходом новых стандартов C++ и появлением их поддержки в компиляторах этот язык должен набрать большую популярность в эмбеддеде.
    Ответ написан
    Комментировать