Я работаю над проектом C99, который требует от нас соблюдения стандарта MISRA C:2012.
Правило 11.3 по существу запрещает приведение указателя между различными типами объектов без объявления обоснованного исключения утвержденным отклонением (в соответствии с требованиями MISRA).
Причиной этого, по-видимому, является предотвращение неопределенного поведения в случае
Мне кажется, что пункт 2 касается строгого правила псевдонимов (по крайней мере, сноска к указанному параграфу C99 указывает на это).
Я бы сказал, что и то, и другое не имеет значения в этом конкретном проекте, потому что...
Я упускаю какое-то потенциально неопределенное поведение или это веское оправдание для нарушения этого правила?
редактировать:
Я согласен с вами, что это запах кода и нарушает переносимость. Более того, я вижу, что в зависимости от того, как и где осуществляется доступ к памяти, проблемы с выравниванием все равно могут быть возможны.
Но просто добавлю немного контекста: я имею дело с кодом, который уже существует и имеет очень плохое обоснование этого отклонения. Изменение его из-за запаха кода на самом деле не вариант из-за накладных расходов на процесс. Но если есть реальная проблема, например. из-за неопределенного поведения это будет другое дело. Если бы я мог полностью переписать код, я бы определенно хотел написать его так, чтобы не нарушать это правило.
Что касается Cortex M7, он не обязательно может обрабатывать несогласованный доступ. См. Developer.arm.com/documentation/dui0646/c/… Как программист на C, вы не можете контролировать, какие инструкции будут сгенерированы, и вам, вероятно, не следует предполагать, что компилятор может обнаружить несогласованный доступ и сгенерировать инструкции соответственно.
Обратите внимание: даже если ядро поддерживает невыровненный доступ, память, к которой вы обращаетесь, может не поддерживать. Современные микроконтроллеры имеют несколько различных областей памяти, и некоторые из них могут иметь больше ограничений доступа, чем другие.
О «Кортексе-М7»: MISRA – это еще и портативность. Даже если ваш процессор способен волшебным образом справиться со всеми вашими недостатками, ваш код потерпит неудачу на другом процессоре, который, возможно, не сможет этого сделать.
Тот, кто «исправил» мою правку, в которой упоминается соответствие MISRA, на соответствие MISRA C:2012, пожалуйста, оставьте это в покое... спасибо!
@Andrew ОП изменил это. И насколько нам известно, ОП может следовать за MISRA C:2012 (с TC: или без них). Лично я до сих пор поддерживаю множество проектов, в которых даже застрял на MISRA C:2004 :/
Основными причинами недопустимости такого приведения являются смещение и «строгий псевдоним», оба из которых вызывают неопределенное поведение. Мы можем это сказать, поскольку в верхней части правила 11.3 есть загадочная заметка.
C11 [Не определено 25, 37]
Это странная, несколько подозрительная вещь MISRA, когда вы должны открыть Приложение J ISO C11 и внести там сводный список UB.
(Что сомнительно, поскольку Приложение J является информативным и, как известно, наполнено ошибками, которые никогда не исправляются, многие из них сидят там с C99. Во многих Приложениях J говорится, что что-то в них является UB, а затем указываются на совершенно не относящиеся к делу нормативные части, которые не обосновывать или даже упоминать предполагаемое УБ вообще нельзя доверять - могу накопать в нем кучу разных ошибок, если подскажут.)
«Неопределенное 25» — это UB смещения, а «Неопределенное 37» — это UB эффективного типа/строгого псевдонима.
Примечательно, что правило 11.3 имеет исключение для преобразования в указатель на символьный тип (но не из него), поскольку тогда не применяются ни выравнивание, ни строгое псевдонимирование.
Правомерно ли отклонение?
Во встроенных системах бывают редкие странные ситуации, когда могут быть мотивированы дикие и сумасшедшие приведения указателя. Например, драйверы Flash/eeprom могут потребовать, чтобы вы написали C, что сводится к 16-битному или 32-битному доступу в зависимости от входящего потока байтов.
Другой веской причиной отклонения от правила могут быть преобразования указателя объекта из первого члена структуры в указатель на эту структуру или из указателя на эту структуру, что четко определено в C и является распространенным вариантом использования, но не указано как исключение в 11.3.
Другие ситуации могут возникнуть при реализации таких вещей, как memcpy
или пулов памяти в C, и вам нужно набрать каламбур от типа символа к более крупному типу.
базовый процессор (Cortex-M7) может обрабатывать невыровненный доступ
Да нет Может быть? Я не эксперт по ассемблеру ARM, но, как отмечалось в комментариях, M7 разрешает невыровненный доступ только в некоторых ситуациях, и мы не можем контролировать, какие инструкции по сборке генерируются из C. Итак, если вы хотите воспользоваться инструкциями в ссылке вам нужно написать встроенный ассемблер, и в этом случае правила языка C и MISRA C выходят за рамки.
анализ псевдонимов на основе типов и соответствующая строгая оптимизация псевдонимов отключены
Объем возможного анализа псевдонимов, который вы можете отключить, сильно варьируется в зависимости от компилятора. Возможно, будет сложно доказать, что «да, я определенно все это отключил». Звучит как миссия невыполнима, например, при использовании gcc.
Я упускаю какое-то потенциально неопределенное поведение или это веское оправдание для нарушения этого правила?
Спасибо, именно такого заявления я и ждал. И, кстати, интересно узнать, что приложение C99 может быть неверным - до сих пор я воспринимал это как «основную истину» и в какой-то момент даже запутался, когда оно ссылалось на целочисленный UB на несколько неожиданный абзац.
@Smith_33 Особенно сомнительным является то, что «значение объекта с автоматической продолжительностью хранения используется, пока оно неопределенно», для которого нет явной поддержки нигде в стандарте, и рабочая группа ISO знает это, поскольку они обсуждали концепцию неопределенности. «шатких ценностей» предостаточно. И все же параграф Приложения J продолжает указывать на бессмысленные главы, от C99 до C23.
@Lundin: Существует множество платформ, где объявление типа register unsigned short x;
присваивает x
32-битный регистр, но усекает любое записанное в него значение до 16 бит. Даже до C99 не было ничего необычного в том, что неинициализированный x
, объявленный, как указано выше, вел себя как значение вне диапазона от 0 до 65535, или для x
, у которого отсутствует спецификатор класса хранения register
, но никогда не использовался его адрес, чтобы вести себя аналогичным образом. В Стандарте нет терминологии для обозначения таких вещей, за исключением того, что они характеризуют как UB все обстоятельства, при которых они могут возникнуть.
@Lundin: объекты автоматической продолжительности хранения, значения которых неопределенны, могут вести себя странно даже в некоторых реализациях до C99, даже несмотря на то, что C89 не классифицировал использование таких объектов как UB, и даже если их типы обычно присваивали значения всем возможным битовым шаблонам. .
@supercat О, я вижу, вы комментировали не вопросы и ответы, а мой комментарий о нарушении Приложения J. Ну, в стандарте есть терминология для этого (начиная с C90), и этот термин имеет неопределенное значение. Доступ к одному из них приводит к неопределенному поведению, а не неопределенному. За исключением особого случая «может быть объявлено как register
/без получения адреса», что является явным UB согласно C99.
В любом случае, дело здесь в том, что эта часть Приложения J совершенно неверна: «Неопределенное поведение» «- Значение объекта с автоматическим сроком хранения используется, пока оно неопределенно (6.2.4, 6.7.9, 6.8)». Ни одна из цитируемых глав не содержит какого-либо соответствующего текста, подтверждающего этот предполагаемый UB.
@Lundin: Согласно правилу «как если бы», учитывая unsigned short x; unsigned long y;
единственный способ y=x;
оставить y
удерживающее значение, которое не находится в диапазоне 0-65535 - как это могло произойти в некоторых реализациях C89 - было бы, если бы это вызвал УБ. С другой стороны, авторы C89 не ожидали, что характеристика чего-либо как UB будет иметь приоритет над чем-либо еще, что будет определять поведение, а просто предполагали это как предупреждение: «Хотя некоторые реализации определяют поведение этой конструкции и, вероятно, им следует продолжать это делать, другие, возможно, нет».
@supercat Если вы читаете беззнаковое короткое сообщение и получаете значение больше USHRT_MAX
, то компилятор совершенно не соответствует C89 или более поздней версии, независимо от того, неопределенное значение или нет.
@Lundin: Если попытка прочитать объект с автоматической длительностью, адрес которого не занят, рассматривается как неопределенное поведение, нет требования, чтобы такое чтение давало значение в пределах диапазона типа. Если бы C89 был предназначен для того, чтобы охарактеризовать как UB любые действия, которые будут вести себя странно в некоторых существующих реализациях, то приложение J2 описывало бы что-то, что должно было быть в нормативном тексте, чтобы поддержать это намерение.
@Lundin: См. пример godbolt.org/z/5z3cG1hcb; эта версия компилятора появилась после публикации C99, но тот факт, что test1
рассматривается так же, как test2
, не удивил бы людей, знакомых с MIPS или аналогичной платформой в 1980-х или 1990-х годах.
Помимо упомянутых причин, приведение типов объектов к разным типам также может быть «запашком кода» (приведение к
unsigned char*
/uint8_t*
является допустимым исключением). Итак, вам следует спросить: какое в первую очередь действительное оправдание для таких приведения указателей? Каков вариант использования и почему вы не можете решить эту проблему с помощью лучшего дизайна вместо приведения?