Как работает данный код

Тип указателя компилятор проверяет, контроллеру пофигу, ему главное адрес дай и операцию сообщи.

Так что правы все ))

“В памяти” т.е. в уже откомпилированной программе - да, а пока работает компилятор, для него - нет. Т.е. в процессе преобразования из исходника в объектный код - информация о типе сохраняется и используется, а по окончании работы компилятора - забывается (т.к. уже нашла отражение в объектном коде).

вставив например строку, принудительно присваивая указателю значение другого указателя (по факту адреса), я их делаю равными независимо от типа данных

unsigned short * buf16 = (unsigned short*) buf8;

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

Это очень жестокая и ошибкоопасная операция - наследие “честного С”. В С++ предпочтительнее использовать частные операции xxx_cast

а я с malloc()-ом всегда так делаю

char *buf = (char *)alloc(30);

Это прям плохо-плохо?

Ну, в целом, как и везде, если знаешь, что делаешь, то нормально. А вот, в случае с ТС, например …

Сейчас я подготовлю пример, который поясняет, где тут могут прятаться грабли (на примере проблемы ТС в этом топике). Как будет готов, выложу.

т е например если я на вход функции использую указатель на char 8и битный
то внутри функции чтоб получить указатель На 16и битные данные пишу так?

unsigned short * buf16 = static_cast<unsigned short *>buf8;

для указателей
unsigned short * buf16 = reinterpret_cast<unsigned short *>(buf8);

спорить не буду, но читал вот что:

Операция reinterpret_cast доступна только в C++ и является наименее безопасной формой приведения типов данных в С++, она позволяет интерпретировать значение в другой тип данных. reinterpret_cast не должна быть использована для приведения иерархии классов или преобразования константных переменных.
Операция static_cast доступна только в языке C++. static_cast может быть использована для преобразования одного типа в другой, но она не должна быть использована для выполнения недопустимого преобразования, например, преобразование значения в указатель или наоборот. Рекомендуется пользоваться операцией static_cast, нежели Cи-стилем приведения, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.

Ай, пусть Петрович обьяснит, у него голова больше, моё скудоумие не простирается так далеко. Я привык обычные типы в друг друга преобразовывать через static_cast, указатели через reinterpret, константы через const_cast.

кста, там у тебя прям написано, что если вдруг у тебя в переменной хранится адрес (например ячейки EEPROM), то через static_cast ты его к указателю не приведёшь, а reinterpret-ом запросто.

Ну, в общем, про преобразование типов.

Я буду ссылаться на разделы стандарта и указывать номера страниц по файлу, а не по написанным на страницах номерах (они отличаются).

Вообще, существуют следующие способы преобразования типов:

  1. const_cast (8.2.11, стр. 131)
  2. static_cast (8.2.9, стр. 127)
  3. reinterpret_cast (8.2.10, стр. 129)
  4. dynamic_cast (8.2.7, стр. 125)
  5. функциональный стиль (8.2.3, стр. 123)
  6. и старый добрый С-стиль, который называется “cast notation” (8.4, стр. 141)

Подробно расписывать, что какой делает и разницу между ними я не буду, т.к. текст не будет понятен большей части аудитории. Любые попытки объяснить это просто (например, одна из лучших по мне попыток, или вот здесь, тоже ничего, ну, ещё здесь гляньте), на мой взгляд, ничего не проясняют, а только добавляют тумана.

Для нашего рассмотрения я буквально одной фразой помечу особенности частных преобразований (№№ 1-4 в списке выше). Но, предупреждаю - это недопустимое упрощение, я лишь говорю то, что мне нужно будет в дальнейшем.

  • const_cast - ничего особо не преобразовывает, но может отменить/добавить модификаторы const и volatile;
  • static_cast - преобразует статический объект к другому (например, при преобразовании указателей на наследуемый класс, числовое значение указателя может измениться, чтобы указывать на свойства нужного класса в общем списке свойств см. статью). Не трогает const и volatile;
  • dynamic_cast - то же, что и предыдущее, но при этом контролирует, что указатель действительно указывает на то, к чему его приводят. Не трогает const и volatile;
  • reinterpret_cast - очень грубое преобразование. По сути - это приказ компилятору: “заткнись и считай, что там такой тип, как я сказал”. Не трогает const и volatile.

Что у нас было в примере ТС? Он пытался привести указатель на литерал к типу char *. Попробуем это сделать при помощи перечисленных преобразователей:

char * yin = static_cast<char *>("00");
char * yang = dynamic_cast<char *>("00");	
char * dao = reinterpret_cast<char *>("00");
char * ohh = const_cast<char *>("00");
Ругань компилятора
kaka.ino: In function 'void setup()':
kaka.ino:2:39: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
  char * yin = static_cast<char *>("00");
                                       ^
kaka2:3:41: error: cannot dynamic_cast '"00"' (of type 'const char [3]') to type 'char*' (target is not pointer or reference to class)
  char * yang = dynamic_cast<char *>("00");
                                         ^
kaka2:4:44: error: reinterpret_cast from type 'const char*' to type 'char*' casts away qualifiers
  char * dao = reinterpret_cast<char *>("00");
                                            ^
exit status 1
cannot dynamic_cast '"00"' (of type 'const char [3]') to type 'char*' (target is not pointer or reference to class)

Как видим, без ругани проскочил только const_cast. Не сработал даже reinterpret_cast, который, на самом деле очень грубый и брутальный!

Это должно было бы навести нас на мысль, что в действительности такое преобразование типа просто некорректно и им лучше не пользоваться! Но, нет, мы “умные”! Мы не пользуем эти новомодные штучки, а воспользуемся преобразованием в С-стиле!

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

  1. const_cast
  2. static_cast
  3. static_cast и следом const_cast
  4. reinterpret_cast
  5. reinterpret_cast и следом const_cast

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

Вот у нас const_cast и сработал! Но ведь const_cast просто тупо отбросил const и превратил литерал в изменяемую переменную (что потом ТС и расхлёбывал), но нам пофиг! Главное, преобразование прошло и компилятор не ругается!

Итак, главная проблема C-стиля: если преобразование сработало (компилятор не ругнулся), то мы на самом деле не знаем что именно сработало и что он там напреобразовывал!

А вот если бы ТС делал частными преобразованиями, он бы сразу понял, что единственный способ преобразовать - это наплевать на константность литерала. И, возможно, это бы его остановило и уберегло бы от недельной головной боли на тему “кто мои данные портит?”.

Ну, вот, как-то так.

2 лайка

Спасиба, оч. интересно, надо почитать/разобрацца. :slight_smile:

на предупреждение

warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

частенько натыкаюсь при написании строковых функций, особенно при тестировании, когда пихаю функции тестовые данные, т е

foo("hlam");

void foo(char * inStr) {
.....
}

тупо в вызове пишу

foo((char*)"hlam");

и забываю об этом. Потом, уже в реальном конечном коде, в функцию отправляю указатели буферов/данных и все хорошо.

вопрос:
может быть правильнее объявлять функцию так:

void foo(char inStr[]);

?

Если функция НЕ собирается изменять буфер, то правильнее объявлять:

void foo(const char * inStr)

а если собирается, то в корне неправильно пихать в неё литерал, как в

foo("hlam");
3 лайка