В настоящее время я изучаю указатели, и мой профессор привел этот фрагмент кода в качестве примера:
//We cannot predict the behavior of this program!
#include <iostream>
using namespace std;
int main()
{
char * s = "My String";
char s2[] = {'a', 'b', 'c', '\0'};
cout << s2 << endl;
return 0;
}
Он написал в комментариях, что мы не можем предсказать поведение программы. Но что именно делает его непредсказуемым? Я не вижу в этом ничего плохого.
s
программа, принятая каким-либо компилятором, формально ведет себя непредсказуемо.Ответы:
Поведение программы не существует, потому что оно плохо сформировано.
Это незаконно. До 2011 года он был устаревшим на 12 лет.
Правильная строка:
В остальном программа в порядке. Ваш профессор должен пить меньше виски!
источник
Ответ: это зависит от того, по какому стандарту C ++ вы компилируете. Весь код идеально сформирован по всем стандартам ‡ за исключением этой строки:
Теперь строковый литерал имеет тип,
const char[10]
и мы пытаемся инициализировать на него неконстантный указатель. Для всех других типов, кромеchar
семейства строковых литералов, такая инициализация всегда была недопустимой. Например:Однако до C ++ 11 для строковых литералов было исключение в §4.2 / 2:
Итак, в C ++ 03 код прекрасен (хотя и не рекомендуется) и имеет четкое, предсказуемое поведение.
В C ++ 11 этого блока не существует - такого исключения для преобразованных строковых литералов нет
char*
, поэтому код так же плохо сформирован, как иint*
приведенный мною пример. Компилятор обязан выдавать диагностику, и в идеале в таких случаях, как этот, которые являются явным нарушением системы типов C ++, мы ожидаем, что хороший компилятор не только будет соответствовать в этом отношении (например, выдав предупреждение), но и не сможет прямо.В идеале код не должен компилироваться, но он выполняется как для gcc, так и для clang (я предполагаю, потому что, вероятно, существует много кода, который будет сломан с небольшим выигрышем, несмотря на то, что эта системная дыра не рекомендуется более десяти лет). Код плохо сформирован, и поэтому нет смысла рассуждать о том, каким может быть поведение кода. Но, учитывая этот конкретный случай и его ранее разрешенную историю, я не считаю необоснованным интерпретировать полученный код, как если бы он был неявным
const_cast
, например:С остальной частью программы все в порядке, поскольку вы больше никогда не прикасаетесь к ней
s
. Чтение созданногоconst
объекта через неconst
указатель - это нормально. Запись созданногоconst
объекта через такой указатель является неопределенным поведением:Поскольку
s
нигде в вашем коде нет никаких изменений , программа работает в C ++ 03, не должна компилироваться в C ++ 11, но все равно делает - и, учитывая, что компиляторы это позволяют, в ней все еще нет неопределенного поведения † . С учетом того, что компиляторы все еще [неверно] интерпретируют правила C ++ 03, я не вижу ничего, что привело бы к «непредсказуемому» поведению. Напишитеs
хотя бы, и все ставки отменены. Как в C ++ 03, так и в C ++ 11.† Хотя, опять же, по определению плохо сформированный код не дает ожидаемого разумного поведения
‡ За исключением того, что см . Ответ Мэтта Макнабба.
источник
В других ответах говорилось, что эта программа плохо сформирована в С ++ 11 из-за присвоения
const char
массива объектуchar *
.Однако программа была плохо сформирована и до C ++ 11.
В
operator<<
перегруженных в<ostream>
. Требованиеiostream
включенияostream
было добавлено в C ++ 11.Исторически сложилось так, что большинство реализаций так или иначе
iostream
включалиostream
, возможно, для простоты реализации или, возможно, для обеспечения лучшего QoI.Но было бы
iostream
правильно определять толькоostream
класс без определенияoperator<<
перегрузок.источник
Единственная небольшая ошибка, которую я вижу в этой программе, заключается в том, что вы не должны назначать строковый литерал изменяемому
char
указателю, хотя это часто принимается как расширение компилятора.В остальном эта программа кажется мне четко определенной:
cout << s2
), четко определены.operator<<
сchar*
(илиconst char*
).#include <iostream>
включает<ostream>
, что, в свою очередь, определяетoperator<<(ostream&, const char*)
, поэтому кажется, что все на месте.источник
Вы не можете предсказать поведение компилятора по причинам, указанным выше. (Он должен не скомпилироваться, но может и нет.)
Если компиляция завершается успешно, поведение четко определено. Вы, конечно, можете предсказать поведение программы.
Если не удается скомпилировать, значит, программы нет. На компилируемом языке программа является исполняемым файлом, а не исходным кодом. Если у вас нет исполняемого файла, у вас нет программы, и вы не можете говорить о поведении чего-то, чего не существует.
Поэтому я бы сказал, что утверждение вашего профессора неверно. Вы не можете предсказать поведение компилятора, столкнувшись с этим кодом, но это отличается от поведения программы . Так что, если он собирается выбирать гниды, ему лучше убедиться, что он прав. Или, конечно, вы могли неправильно процитировать его, и ошибка заключается в вашем переводе того, что он сказал.
источник
Как отмечали другие, код незаконен в C ++ 11, хотя он был действителен в более ранних версиях. Следовательно, компилятор для C ++ 11 должен выдавать по крайней мере одну диагностику, но поведение компилятора или остальной части системы сборки не определено сверх этого. Ничто в Стандарте не запрещало бы компилятору внезапно завершить работу в ответ на ошибку, оставив частично записанный объектный файл, который компоновщик мог бы счесть действительным, что привело бы к повреждению исполняемого файла.
Хотя хороший компилятор перед завершением работы всегда должен гарантировать, что любой объектный файл, который он должен создать, будет действительным, несуществующим или распознаваемым как недопустимый, такие проблемы выходят за рамки юрисдикции Стандарта. Хотя исторически были (и могут быть) некоторые платформы, на которых неудачная компиляция может привести к появлению законно выглядящих исполняемых файлов, которые произвольно вылетают при загрузке (и мне приходилось работать с системами, в которых ошибки ссылок часто имели такое поведение) , Я бы не сказал, что последствия синтаксических ошибок вообще непредсказуемы. В хорошей системе попытка сборки обычно либо создает исполняемый файл с максимальными усилиями компилятора при генерации кода, либо вообще не создает исполняемый файл. Некоторые системы оставляют старый исполняемый файл после неудачной сборки,
Я лично предпочитаю, чтобы дисковые системы переименовали выходной файл, чтобы учесть те редкие случаи, когда этот исполняемый файл был бы полезен, избегая путаницы, которая может возникнуть из-за ошибочного убеждения, что кто-то запускает новый код, и для встроенного программирования системы, позволяющие программисту определять для каждого проекта программу, которая должна быть загружена, если действительный исполняемый файл недоступен под обычным именем [в идеале то, что безопасно указывает на отсутствие пригодной программы]. Набор инструментов для встроенных систем, как правило, не имеет возможности узнать, что должна делать такая программа, но во многих случаях кто-то, пишущий «настоящий» код для системы, будет иметь доступ к некоторому коду аппаратного тестирования, который можно легко адаптировать к цель. Я не знаю, видел ли я поведение переименования, однако
источник