2015-08-22 2 views
4

Когда я answered это question, я писал:Является ли поведение компилятора неопределенным, с неопределенным поведением?

Во-первых, важно отметить, что это не только поведение программы пользователя, которая является неопределенным, это поведение компилятора, который не определен.

Но был disagreement in a comment, поэтому я хочу, чтобы задать вопрос здесь:

Если исходный код содержит неопределенное поведение, это только поведение переведенного машинного кода, который не определен, или является поведение компилятора не определено?

Стандарт определяет поведение абстрактной машины (1.9):

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

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


Более практичный вариант этого вопроса будет:
Предположим, компилятор врежется или не выводит, когда он находит UB на всех путях управления, как в этой программе:

int main() { 
    complex_things_without_UB(); 
    int x = 42; 
    x = x++; //UB here 
    return x; 
} 

но в противном случае он всегда будет создавать корректные двоичные файлы. Будет ли это все еще стандартным компилятором?

+2

Стандарт на самом деле не имеет понятия «компилятор», AFAIK. –

+1

Не последнее ли последнее подразумевает? Если у компилятора есть неопределенное поведение, полученный машинный код (и, следовательно, пользовательская программа) также не определен, верно? – JorenHeit

+0

@OliverCharlesworth Я согласен, но что это значит? – alain

ответ

5

Стандарт C++ определяет поведение для кода, он не определяет поведение для компилятора. Таким образом, на самом деле не имеет смысла ссылаться на неопределенное поведение компилятора - он никогда не был четко определен для начала. Единственное требование состоит в том, что он создает реализацию, которая соответствует стандартным правилам для кода. Как он делает это деталь реализации.

+0

, но он налагает требования на реализацию, а поведение для кода является (частью) этих требований. – davmac

3

Это довольно размытая линия в целом. Дело в том, что исходный код не имеет определенного поведения, что означает, что поведение сгенерированного кода недостаточно четко определено.

Компилятор должен, по всем счетам, вести себя определенным образом - но, конечно, это может быть скорее «случайным» (например, компилятор может выбрать случайное число в ваш расчет) или даже вызов rand - и он по-прежнему полностью соответствует правам компилятора). Конечно, случаи, когда компилятор (ab) использует тот факт, что он знает, что что-то не определено для оптимизации.

Я бы счел это очень плохой реализацией компилятора, если, например, компилятор сработает или приводит к форматированию жесткого диска, но я считаю, что компилятор может быть «прав», если он говорит: «Это undefined, я отказываюсь компилировать его "[в некотором роде].

Конечно, есть (довольно много) ситуаций, когда что-то не определено, а не потому, что сама конструкция не определена, а потому, что «трудно определить одно поведение, которое можно реализовать во многих местах» - для например, использование недопустимого указателя (int* p = (int*) rand(); или без использования) не определено, но компилятор может не знать и понимать, правильно это или нет. Напротив, это зависит от архитектуры процессора, что происходит, если вы используете указатель на случайном адресе или после его освобождения. Оба случая могут привести к сбою на одной машине, а не к сбою, но ошибочному результату по другому, а в некоторых случаях «вы не заметите, что что-то не так». Это явно не поведение компилятора, которое не определено, но результирующая программа.

2

Это только поведение переведенного машинного кода, который является неопределенным, или поведение неопределенного поведения компилятора тоже?

ISO C и C++ описывают, как выглядят программы на C и C++. Они не описывают окружение . Мы обычно используем компилятор , чтобы обратиться к инструменту, который переводит C и C++ в машинный код; формально, однако, используемый термин является реализация, который определенно шире.

Таким образом, единственное, что не определено, является одной из программ. Это также дает определение UB:

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

+0

Ваша цитата не поддерживает ваши аргументы. _Использование «непереносимой или ошибочной программы» - это не то же самое, что _execution_ указанной программы. – davmac

+0

@davmac Если я понимаю, что вы имеете в виду, «программа» не является существительным, а прилагательным. – edmz

+0

Я полагаю, что я запутал проблему, ограничив мою цитату «программой», а не полной «конструкцией программы», как она появляется в тексте. Я хочу сказать, что «использование конструкта программы» также может легко ссылаться на наличие этой конструкции в исходном коде, когда она скомпилирована так, как она может быть использована для выполнения такой программы. Я считаю, что подчеркивание слова «программа» в одиночку, как вы это сделали, неверно. – davmac

1

Предполагая, что «неопределенное поведение для компилятора» означает «нет требований к поведению исполняемого файла p rogram ", то поведение компилятора не определено при представлении исходного кода, содержащего неопределенные конструкторы поведения.

Сравните это с поведением компилятора с правильным исходным кодом. Все компиляторы, придерживающиеся стандарта, должны создавать исполняемый код с эквивалентным поведением, который определяется стандартом для правильного исходного кода.

+0

Если программа содержит какие-либо директивы '# pragma', это было бы законно, с точки зрения Стандарта, просто для того, чтобы скомпилировать ее для вызова носовых демонов. – supercat

2

Если код имеет неопределенное поведения это означает, что стандарты не знают, как справиться с такой вещи. Таким образом, он может выдавать любой результат. Я думаю, что это не связано с компилятором, поскольку это не имеет смысла. Имеет смысл, что это должна быть реализация , которая работает в соответствии со стандартами.

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

0

Нет компилятор, указанный в стандарте и детали реализации, зависит от поставщиков.

Стандарт определяет , как код должен вести себя (синтаксически и семантически) и/или ограничен в терминах сложности относительно некоторых стандартных библиотечных алгоритмов. Исходный код не должен иметь точного поведения (и это нигде не определено). Каждый компилятор просто должен создать код, который в правиле как-если верен.

Это не имеет смысла ссылаться на неопределенное поведение компилятора

+0

Замечание относительно «нет компилятора, упомянутого в стандарте», «компилятор» появляется в 6 раз в спецификации C11. – chux

+0

@chux Я считаю, что термин «компилятор» используется только в ненормативных частях спецификации (сноски и т. Д.). Однако термин «перевод», который используется более формально, возможно, можно приравнять к компиляции. – davmac

1

Мой собственный тейк является то, что поведение в «неопределенное поведение» является то, что в реализации. Спецификация относится к процессу «перевода», который мы можем приравнивать к компиляции, но тот факт, что вы можете скомпилировать программу для исполняемого кода, здесь не уместен, результат по-прежнему считается частью реализации, по крайней мере, в качестве как поведение. Обратите внимание, что, хотя спецификация действительно определяет, как будет работать программа на C, когда она устанавливает требования к реализации, а поведение программы также может считаться требованием (или набором требований) к реализации.

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

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

«Прекращение перевода» четко указывает на сбой компиляции, тогда как «завершение выполнения ...» явно относится к поведению запущенной программы.

См. Также Приложение J.2, в котором перечислены примеры неопределенного поведения. Среди примеров:

Исходный файл непустым не заканчивается в символ новой строки, которая не непосредственно предшествует символ обратной косой черты или заканчивается в частичном лексемы препроцессора или комментарий (5.1.1.2)

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

+0

Первый абзац, который вы указали, является именно тем, что заставляет меня думать, что UB также относится к компилятору. – alain

+0

Некоторые компиляторы, если задано файл '# include', последняя строка которого не была правильно завершена, будет конкатенировать строку, следующую за директивой' # include', и обработать результат как одну строку. Возможно, какой-то код использует это, и авторы Стандарта не хотели нарушать такой код. Вместо того, чтобы пытаться перечислить все способы, с помощью которых компиляторы могли бы интерпретировать такие вещи (например, делает ли конец пробела как пробел, каково должно быть расширение макроса __FILE__ или __LINE__ в самом конце файла и т. Д.), Авторы Стандарт просто позволяет компиляторам создавать свои собственные правила. – supercat