Полагается ли на GCC / LLVM `-fexceptions` технически неопределенное поведение? - PullRequest
1 голос
/ 23 апреля 2019

Насколько я могу судить, расширения компилятора могут рассматриваться как не определено , а не определяется реализацией .Я предполагаю (но не знаю наверняка), что это относится к стандарту C ++, а также к стандарту C.

И GCC, и LLVM предлагают функцию -fexceptions, которая, по-видимому, обеспечивает исключение из C ++код через C code , а затем перехват его в коде C ++ будет вести себя как положено, то есть разматывать кадры стека в C и C ++ и вызывать деструкторы для локальных элементов C ++.(Примечание: я понимаю, что ресурсы, выделенные в разматываемых фреймах стека C, не будут освобождены. Это не часть моего вопроса.) Вот соответствующий текст из документации GCC :

Если вы не укажете эту опцию, GCC включает ее по умолчанию для языков, таких как C ++, которые обычно требуют обработки исключений, и отключает ее для языков, подобных C, которые обычно не требуют ее.Однако вам может потребоваться включить эту опцию при компиляции кода C, который должен корректно взаимодействовать с обработчиками исключений, написанными на C ++.

Однако я не могу найти ничего в стандартах C или C ++, указывающих, какследует ожидать, что раскручивание будет взаимодействовать со стеком, содержащим кадры, скомпилированные из разных исходных языков.Стандарт C ++, по-видимому, упоминает только раскрутку в 15.2, except.ctor, которая просто объясняет правила, касающиеся уничтожения локальных объектов при возникновении исключения.

Следовательно, передача исключения через неопределенное поведение кода C, даже используярасширение языка, предназначенное для того, чтобы оно работало четко определенным образом?Является ли использование такого расширения, предоставленного реализацией, «неправильным»?

Для контекста этот вопрос вдохновлен двумя довольно длительными дискуссиями в сообществе Rust о разворачивании стека с помощью кода C:

Ответы [ 3 ]

2 голосов
/ 26 апреля 2019

Опираясь на документацию по реализации

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

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

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

Если вы спросите, является ли передача исключения через код C «неопределенной» в смысле, используемом в стандартах C или C ++, ответ будет положительным. Но эти стандарты только обсуждают то, что они определяют. Использование ими «неопределенного» не запрещает кому-либо еще определять поведение. Фактически, если вы используете документацию реализации, у вас есть определение для поведения. Стандарты C и C ++ не отменяют, не отменяют и не аннулируют определения, сделанные другими документами:

  • Там, где стандарт C или C ++ говорит, что любое поведение не определено, это означает лишь то, что поведение не определено в контексте стандарта C или C ++.
  • Любая другая спецификация, которую программист выберет для использования, может определять дополнительное поведение, которое не определено стандартом C или C ++. Стандарты C и C ++ не запрещают этого.
* * Пример 1 022

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

  • Стандарт С.
  • Стандарт C ++.
  • Инструкция по сборке.
  • Документация компилятора.
  • Документация Apple Developer Tools, включая поведение Xcode, компоновщика и других инструментов, используемых при сборке программного обеспечения.
  • Руководства по процессорам.
  • Спецификации архитектуры набора инструкций.
  • Стандарт IEEE-754 для арифметики с плавающей точкой.
  • Документация Unix для инструментов командной строки.
  • Документация Unix для системных интерфейсов.

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

Написание портативного кода

Любая софтваПроект или любой инженерный проект выполняется из помещений: он принимает различные технические характеристики инструмента, свойства материала, свойства устройства и т. д. и получает нужные продукты из этих помещений.Редко ли какой-либо законченный коммерческий продукт для конечного пользователя полагается исключительно на стандарт C или C ++.Когда вы покупаете iPhone, он подчиняется законам физики, и вы имеете право полагаться на него, чтобы он соответствовал требованиям безопасности для электрических устройств и поведениям на радиочастотах, регулируемым государственными органами.Он соответствует многим спецификациям, и представление о том, что стандарт С должен рассматриваться как превосходящий эти другие спецификации, абсурдно.Если ваше устройство загорелось из-за ошибки программирования, которая, согласно стандарту C, имеет неопределенное поведение, это неприемлемо - тот факт, что стандарт C говорит, что оно не определено, не превосходит спецификацию безопасности.

Даже вчисто программные проекты, очень немногие строго соответствуют стандартам C или C ++.В основном, только программное обеспечение, которое выполняет некоторые чистые вычисления и ограничивает ввод / вывод, может быть написано на строго соответствующем C или C ++.Это может включать в себя очень полезные библиотеки, которые включены в другое программное обеспечение, но включает в себя очень мало готовых коммерческих программ для конечных пользователей, например, некоторые вещи, используемые математиками и учеными, например, для ответов на вопросы о логике, математике и моделировании.Большая часть программного обеспечения в этом мире взаимодействует с устройствами и операционными системами способами, не определенными стандартами C или C ++.В большинстве программ используются расширения, не определенные стандартами - расширения, которые управляют файлами и памятью способами, не определенными стандартами, которые взаимодействуют с устройствами и пользователями способами, не определенными стандартами.Они отображают окна графического интерфейса и принимают ввод от мыши и клавиатуры от пользователя.Они передают и получают данные по сети.Они посылают радиоволны на другие устройства.

Эти вещи невозможны без использования поведения, не определенного языковыми стандартами.И если бы языковые стандарты превзошли определения этого поведения, написание такого программного обеспечения было бы невозможно.Если вы хотите отправить радиосигнал Wi-Fi, и вы приняли стандарт C, а стандарт C превзошел другие определения, это означало бы, что вы не сможете написать программное обеспечение, которое надежно отправляет радиосигнал.Очевидно, что это не так.Стандарт C не превосходит другие спецификации.

Написание «переносимого кода» не является практически осуществимым требованием для большинства программных проектов.Конечно, желательно содержать непереносимый код для очистки интерфейсов.Желательно написать, какой код можно использовать с помощью переносимого кода, чтобы его можно было использовать повторно.Но это только часть большинства проектов.Для большинства проектов проект в целом должен использовать поведение, определяемое документами, отличными от языковых стандартов.

2 голосов
/ 23 апреля 2019

В том смысле, что C не определяет, что происходит, когда вы вызываете функцию, написанную на языке, отличном от C, и гораздо меньше того, что происходит, если эта функция не может быть возвращена, а вместо этого заканчивает свое время жизни и время жизни C вызывающий другим способом, да, это неопределенное поведение . Это , а не «поведение, определяемое реализацией», поскольку определяющая характеристика поведения, определяемого реализацией, заключается в том, что языковой стандарт налагает требование к реализациям на то, что они документируют конкретное поведение, а здесь это не так; рассматриваемая тема полностью выходит за рамки соответствующего стандарта.

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

1 голос
/ 26 апреля 2019

Код не является UB, потому что код не на C ++ языке, код на C ++ с языком расширений gcc / clang .В C ++ с расширениями gcc / clang код задокументирован и четко определен.В C ++ тот же код будет UB.

Так что, если вы возьмете тот же код и скомпилируете его в чистом стандарте C ++, тогда этот код будет иметь UB.Но если вы скомпилируете его в C ++ с расширениями gcc / clang, то код будет хорошо определен.

...