Имеет ли следующая программа неопределенное поведение?
Код: Выделить всё
#include // std::{ostream, streambuf}
// The streambuf ctor is protected so we need a wrapper to create one.
struct mystreambuf : public std::streambuf {};
extern mystreambuf sb; // Not yet constructed.
std::ostream os(&sb); // Passing "invalid" pointer here? UB?
mystreambuf sb; // Now it is constructed.
int main() { return 0; }
Код: Выделить всё
streambuf(basic.life p1).
Означает ли это неопределенное поведение?
Попытка ответаЕсли быstreambuf был написанным пользователем классом, то управлял бы
class.cdtor p1
, что гласит:
Для объекта с нетривиальным конструктором обращение к любому
нестатическому члену или базовому классу объекта до того, как конструктор
начнет выполнение, приводит к неопределенному поведению. [...]
Этот язык и сопровождающий его пример ясно дают понять, что просто
взятие адреса не созданного объекта не является неопределенный. Насколько
я могу судить, передача этого адреса в качестве указателя на написанную пользователем функцию
, которая только сохраняет свое значение и проверяет его на соответствие nullptr, также не является
неопределенной.< /p>
Ноstreambuf — это библиотечный класс, поэтому вместо него применяется
res.on.arguments p1
, в котором, в частности, говорится:
Если аргумент функции имеет недопустимое значение (например, значение
вне домена функции или указатель, недопустимый для его
предполагаемого использования ), поведение не определено.
Но что представляет собой «недопустимое значение»? Предположительно, нам придется
определить «назначение», прочитав спецификацию вызываемой
функции. Спецификация конструктора
ostream.cons p1 частично говорит:
Эффекты: Инициализирует подобъект базового класса с помощью
Код: Выделить всё
basic_ios::init(sb)Спецификация для init
basic.ios.cons p4 говорит:
Постусловия: Постусловия этой функции указаны в
Таблице 127.
где в Таблице 127 есть две строки, в которых упоминается sb:
Код: Выделить всё
Element Value
------- -----
rdbuf() sb
rdstate() goodbit if sb is not a null pointer, otherwise badbit.
(чтобы rdbuf() мог его вернуть) и проверено на nullptr; и
что все это вместе составляет его «назначенное использование». Поскольку и то и другое
было бы допустимо для кода, написанного пользователем, передача рассматриваемого указателя
законна, поэтому программа имеет определенное поведение.
Но Таблица 127 — это всего лишь список постусловий. Он не
окончательно утверждает, что ничто иное не входит в сферу «использования по назначению».
Для этого, казалось бы, необходимо исчерпывающе просмотреть все, что
Код: Выделить всё
basic_ostreamПри попытке сделать это я обнаружил, что imbue находится в
basic.ios.members p9:
Эффекты: вызывает ios_base::imbue(loc) и если rdbuf() != 0, то
Код: Выделить всё
rdbuf()->pubimbue(loc)Очевидно, вызов rdbuf()->pubimbue(loc) перед объектом, на который указывает
rdbuf() не определен. Мы вызываем imbue? Не
конечно, и нет особой причины подозревать
косвенный вызов, но существование такого поведения, возможно, помещает
его в область «назначенного использования» переданного указателя в конструктор
, поскольку в конечном итоге его можно будет использовать таким образом. Более того,
будет ли обязательно несоответствующим вызов реализации
Код: Выделить всё
imbueможет быть, и если реализация может свободно вызывать imbue в
конструкторе, то очевидно, что мы имеем неопределенное поведение. И могут быть
другие методы, которые предполагают другое использование, поскольку мой опрос ни в коем случае не был
полным.
Теперь, в комментарии к
ответ
на связанный вопрос, Инди замечает, что реализация
в Clang
Код: Выделить всё
std::basic_fstreamобъекта в конструктор iostream по адресу
fstream
Код: Выделить всё
basic_filebuf __sb_;
};
template
inline basic_fstream::basic_fstream() : basic_iostream(&__sb_) {}
и (2) реализации библиотеки обычно разрешено делать вещи
, которые могут быть неопределенным в пользовательском коде. Тем не менее, это, по крайней мере, слабое
доказательство того, что разработчики Clang считают, что эта практика не имеет
неопределённого поведения, поскольку у них нет причин в этом случае писать код
полагающийся на лицензию библиотеки. нарушить правила, поскольку было бы
тривиальным изменением вместо этого передать nullptr в конструктор и
затем в теле вызвать init с адресом (теперь полностью
созданный) объект-член.
В конечном итоге мне кажется, что спецификация языка неоднозначна,
поскольку она опирается на термины «недопустимое значение» и «назначенное значение». использование", которые
четко не указаны. Но, возможно, кто-то сможет указать положение, которое я
пропустил, или ошибку в моей интерпретации.
Вопросы по теме
Изучая это, я столкнулся с некоторыми существующими вопросами,
которые казались связанными. Вопрос
Как наследовать от std::ostream?
имеет три релевантных ответа:
[*](набравший наибольшее количество голосов) )
Ответ Бена был
специально отредактирован, чтобы избежать потенциальной проблемы, обеспечив
Код: Выделить всё
streambuf[*]Более свежий
ответ от mach6 также
уходит его способ избежать передачи указателя
несконструированного объекта, на этот раз путем инициализации ostream с помощью nullptr
(хотя и с использованием нестандартного конструктора, который есть только в GNU libc++,
но легко заменяется стандартным) с последующим вызовом init
после этого.
[*] ответ
Хенрика Хейно передает еще не созданный указатель. Но этот ответ
не претендует на правильность и содержит один комментарий, в котором говорится, что передача
указателя таким способом неверна.
Из этих ответов и комментариев я делаю вывод, что немало знающих
людей считают, что пример в верхней части этого вопроса имеет
неопределенное поведение.Между тем, вопрос
Опасно ли передавать указатель на еще не созданный подобъект конструктору другого подобъекта во время построения объекта?
почти такой же, как и вопрос мой, но он испорчен отсутствием некоторых важных
частей кода примера и включает в себя посторонний
Код: Выделить всё
AnotherClassОтвет aschepler
, кажется, говорит, что практика в целом нормальна, но не в случае OP
из-за AnotherClass, но это только рассуждает так, как будто все
код был написан пользователем, игнорируя библиотечный аспект.
Наконец, вопрос:
Безопасно ли передавать несконструированный буфер конструктору std:: ostream?
по сути то же самое, что и мое - прошу дубликат! Почему? Короче говоря, на этот вопрос нет ответов, и я думаю, что дополнительные исследования
в моем вопросе повышают вероятность того, что на мой вопрос можно ответить, поэтому я
фактически отправляю это с намерением заменив этот.
Я задал
мета-вопрос
о том, приемлемо ли задавать этот дубликат, и консенсус
похоже, что это именно так.
Подробнее здесь: https://stackoverflow.com/questions/785 ... buf-object
Мобильная версия