Является ли неопределенным поведение передача указателя на несконструированный объектstreambuf в конструктор ostream?

Вопрос

Имеет ли следующая программа неопределенное поведение?

#include <iostream>          // 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; }

Он вызывает конструктор ostream, передавая указатель на streambuf объект, срок жизни которого еще не начался (basic.life п1 ). Означает ли это неопределенное поведение?

Попытка ответа

Если бы streambuf был классом, написанным пользователем, то class.cdtor p1 будет править, в котором говорится:

Для объекта с нетривиальным конструктором, обращающимся к любому нестатический член или базовый класс объекта перед конструктором начинает выполнение, приводит к неопределенному поведению. [...]

Этот язык и сопровождающий его пример ясно дают понять, что просто получение адреса несконструированного объекта не является неопределенным. Насколько Я могу сказать, передав этот адрес как указатель на написанную пользователем функцию. который только сохраняет свое значение и проверяет его на соответствие nullptr, тоже нет неопределенный.

Но streambuf — это библиотечный класс, поэтому вместо этого res.on.arguments p1 применяется, в котором, в частности, говорится:

Если аргумент функции имеет недопустимое значение (например, значение вне области определения функции или указатель недействителен для ее предполагаемое использование), поведение не определено.

Но что представляет собой «недопустимое значение»? Вероятно, нам придется определить «назначение», прочитав спецификацию вызываемого функция. Спецификация конструктора ostream.cons p1 частично говорит:

Эффекты: Инициализирует подобъект базового класса с помощью basic_ios<charT, traits>::init(sb) ([basic.ios.cons]).

Спецификация для initBasic.ios.cons p4 говорит:

Постусловия: Постусловия этой функции указаны в Таблица 127.

где в Таблице 127 есть две строки, в которых упоминается sb:

Element    Value
-------    -----
rdbuf()    sb
rdstate()  goodbit if sb is not a null pointer, otherwise badbit.

Итак, на первый взгляд может показаться, что sb хранится только (чтобы rdbuf() мог его вернуть) и проверен на предмет nullptr; и что все это вместе составляет его «назначенное использование». Поскольку оба эти было бы законно для кода, написанного пользователем, разрешено передавать рассматриваемый указатель, поэтому программа определила поведение.

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

Пытаясь это сделать, я нахожу imbue на Basic.ios.members p9:

Эффекты: Вызывает ios_base::imbue(loc) и если rdbuf() != 0 то rdbuf()->pubimbue(loc).

Очевидно, что вызов rdbuf()->pubimbue(loc) перед тем, как объект укажет на by rdbuf() построен, не определен. Звоним imbue? Нет явно, конечно, и нет особых оснований подозревать косвенный вызов, но существование такого поведения, возможно, ставит это в рамках «предполагаемого использования» указателя, переданного в конструктор, поскольку в конечном итоге его можно будет использовать таким образом. Более того, обязательно ли будет несоответствием для реализации вызов imbue самостоятельно во время ostream конструктора? Я не понимаю, почему это было бы, и если реализацию можно бесплатно вызвать imbue в конструктор, то, очевидно, мы имеем неопределенное поведение. И там могло бы быть и другие методы, которые предполагают другие варианты использования, поскольку мой опрос ни в коем случае не был полный.

Теперь в комментарии к ответ на связанный вопрос Инди отмечает, что реализация Clang std::basic_fstream передает указатель на несконструированный член объект для конструктора iostream в fstream:1419:

  basic_filebuf<char_type, traits_type> __sb_;
};

template <class _CharT, class _Traits>
inline basic_fstream<_CharT, _Traits>::basic_fstream() : basic_iostream<char_type, traits_type>(&__sb_) {}

Но этот пример не является окончательным, потому что (1) это может быть ошибкой, и (2) реализации библиотеки обычно разрешено делать что-то это будет не определено в пользовательском коде. Тем не менее, это как минимум слабо доказательства того, что разработчики Clang считают, что такая практика не имеет неопределенное поведение, так как у них нет смысла в этом случае писать код который полагается на лицензию библиотеки на нарушение правил, поскольку это будет быть тривиальным изменением, чтобы вместо этого передать nullptr конструктору и затем в теле позвоните init с адресом (теперь полностью построенный) объект-член.

В конечном счете, мне кажется, что спецификация языка неоднозначна, поскольку он опирается на термины «недействительная ценность» и «предназначенное использование», которые четко не указано. Но, возможно, кто-то сможет определить положение, которое я пропустил или ошибка в моих интерпретациях.

Связанные вопросы

Исследуя это, я столкнулся с некоторыми существующими вопросами. это казалось связанным. Вопрос Как наследовать от std::ostream? имеет три соответствующих ответа:

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

  • Более поздний ответь также от mach6 изо всех сил старается избежать прохождения незавершенного объекта указатель, на этот раз инициализируя ostream с помощью nullptr (хотя и с использованием нестандартного конструктора, который есть только в GNU libc++, но легко заменяется на стандартный) и тут звоню init потом.

  • ответ от Хенрик Хейно передает еще не созданный указатель. Но этот ответ не претендует на точность и имеет один комментарий, в котором говорится, что он проходит мимо указатель в этом направлении неверен.

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

При этом вопрос Опасно ли передавать указатель на еще не созданный подобъект конструктору другого подобъекта во время построения объекта? очень почти такой же, как мой, но испорчен некоторыми важными части кода примера отсутствуют и включают в себя посторонний AnotherClass это еще больше запутывает вопрос. ответ от aschepler кажется, говорит, что с практикой в ​​целом все в порядке, но не в ОП случай из-за AnotherClass, но это только так, как будто все код был написан пользователем, игнорируя библиотечный аспект.

Наконец, вопрос Безопасно ли передавать несконструированный буфер конструктору std::ostream? по сути такой же, как у меня - прошу дубликат! Почему? В Короче говоря, на этот вопрос нет ответа, и я думаю, что дополнительные исследования мой вопрос повышает вероятность того, что на мой ответ можно будет ответить, поэтому я фактически отправив это с намерением заменить это. Я спросил метавопрос о том, приемлемо ли спрашивать об этом дубликате, и о консенсусе кажется, что так и есть.


Я принял предложение Криса Додда ответь, но я хочу подробнее об этом, так что это повторение этого ответа в мои собственные слова.

Исходный пример имеет неопределенное поведение, потому что в этой строке:

std::ostream os(&sb);        // Passing "invalid" pointer here?  UB?

выражение &sb имеет тип mystreambuf*, но передается в конструктор, который принимает std::streambuf* и, следовательно, должен пройти преобразование производных в базовые. Это преобразование, примененное к указателю на несконструированный объект с нетривиальным конструктором имеет неопределенное значение поведение, поскольку это «[ссылка] на любой [...] базовый класс объект», что запрещено class.cdtor p1.

Пример в этом разделе дополнительно поясняет. Цитирую ключевые строки от него:

struct X { int i; };
struct Y : X { Y(); };                  // non-trivial
struct A { int a; };
struct B : public A { int j; Y y; };    // non-trivial

extern B bobj;
A* pa = &bobj;                          // undefined behavior: upcast to a base class type
B bobj;                                 // definition of bobj

Более того, это означает, что не только конкретный пример в вопрос не определен, но вообще не определено делать то, что в заголовке вопроса говорится, а именно: «передать указатель на несконструированный streambuf в конструктор ostream». Это потому, что std::streambufКонструктор защищен, поэтому экземпляр всегда должен быть подобъектом базового класса, и, следовательно, единственный способ получить std::streambuf* — использовать производное от базы конверсия.

Это означает, что код, указанный в Clang libc++, будет иметь неопределенное поведение, если бы это был пользовательский код, и я подал Выпуск №93307 против Клана по этому поводу.

Взятие адреса неинициализированного объекта и последующая передача этого указателя по значению разрешены.

user12002570 22.05.2024 06:05

@user12002570 user12002570 Да, это то, что я сказал в самом начале раздела «Попытка ответа». Но, как объясняет остальная часть вопроса, само по себе это наблюдение, я думаю, не дает ответа на вопрос.

Scott McPeak 22.05.2024 06:09

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

user12002570 22.05.2024 06:12

Фиаско порядка инициализации в конечном итоге будет преследовать вас. Статические объекты длительности с динамической инициализацией лучше были бы синглтонами Мейера. Будьте осторожны, чтобы не создать зависимость от циклической инициализации.

Red.Wave 22.05.2024 07:56

это совершенно нормально: godbolt.org/z/KqT7a9vTd но это не так godbolt.org/z/sjno8Ea81. С указателем все в порядке, главный вопрос заключается в том, происходит ли разыменование до того, как появится указатель.

463035818_is_not_an_ai 22.05.2024 08:08

решающий вопрос заключается в том, разыменовывается ли указатель. Ни один из предложенных обманов не касался этого случая с конструктором std::ostream.

463035818_is_not_an_ai 22.05.2024 09:58

@ 463035818_is_not_an_ai в этом ответе говорится о доступе к объекту. Это может быть обман.

user12002570 22.05.2024 10:01

Речь идет о библиотечной функции. Этого достаточно, чтобы отличить его от дураков, как уже говорилось в вопросе.

Weijun Zhou 22.05.2024 10:06

@user12002570 все еще не говорит о std::ostream. Этот вопрос конкретно касается того, что, согласно стандарту, происходит или не происходит в basic_ostream::basic_ostream(basic_streambuf<charT, traits>* sb)

Caleth 22.05.2024 10:07

@user12002570 user12002570 Если вы можете найти обман в отношении этого конкретного конструктора, то это обман. В ответе, который написал Джерри Коффин, говорится о том, что стандарт требует от этого конкретного конструктора.

Caleth 22.05.2024 10:15

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

Weijun Zhou 22.05.2024 10:21

Никто с этим не согласен, но мы не полагаем, что ОП спрашивает об этом.

Weijun Zhou 22.05.2024 10:24

@user12002570 user12002570 мы это уже знаем. Вопрос сводится к тому, разыменовывает ли конструктор ostream указатель.

463035818_is_not_an_ai 22.05.2024 10:26

@user12002570 user12002570 Я считаю, что название вопроса точное. «Неопределенное поведение для выполнения X» спрашивает, есть ли какое-либо неопределенное поведение при оценке X, а не только непосредственно видимые части.

Caleth 22.05.2024 10:32

@ user12002570 НЕТ, эти дураки не решают проблему ОП. Глядя на определение basic_ostream::basic_ostream(basic_streambuf<charT, traits>* sb) и понимая его, можно решить проблему ОП.

Caleth 22.05.2024 10:33

@463035818_is_not_an_ai "пожалуйста, не спамьте вопросами при таком обсуждении..." Для обсуждения требуется более 1 человека. Вы (и другие) виноваты в этой дискуссии не меньше, чем они. То есть вы еще и спамите раздел комментариев подобными обсуждениями.

Alan 22.05.2024 10:58

У init нет видимой причины разыменовывать свой аргумент, и доступные реализации этого не делают, однако ничто в стандарте, похоже, не запрещает это.

n. m. could be an AI 22.05.2024 10:59

@ 463035818_is_not_an_ai У вас также есть возможность удалить свои комментарии. Если вы не любите спамить в разделе комментариев, вы также можете просто удалить свои комментарии. Каждый пользователь может удалить свой комментарий в любое время. Обвинять в том, что ваши комментарии больше не имеют смысла, неразумно, потому что вы можете просто удалить свои комментарии. Если вы не удаляете их сознательно, вы признаете, что также рассылаете спам в этот раздел.

Alan 22.05.2024 11:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
18
286
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Язык, который вы процитировали

Для объекта с нетривиальным конструктором обращение к любому нестатическому члену или базовому классу объекта до начала выполнения конструктора приводит к неопределенному поведению. [...]

может показаться, что это неопределенное поведение - вы ссылаетесь на базовый класс (std::streambuf) объекта до запуска конструктора. То, что происходит в конструкторе ostream, не имеет значения.

Итак, ответ @JerryCoffin верен, но против него есть возражение на том основании, что, хотя стандарт четко определяет, что basic_ios::init() делает, он не указывает, чего он не делает. Итак (возражение звучит), хотя стандарт утверждает, что единственное, что basic_ios::init() делает с переданным указателем, — это сравнивает его с nullptr и сохраняет его… он также может разыменовать его, что вызовет UB в описанной ситуации.

Хорошо, давайте предположим, что логика имеет смысл.

Итак, поскольку basic_ios::init() «может» разыменовать указатель и поскольку конструктор basic_ostream вызывает basic_ios::init(), мы не можем передать указатель члену. Итак, мы не можем этого сделать:

class myostream :
    public std::ostream
{
    std::streambuf _buf;

public:
    myostream() : std::ostream{&_buf} {}

    // other stuff...
};

Потому что, хотя стандарт определяет, что постусловия проводника ostream (косвенно/транзитивно) просто сравнивают указатель, переданный в nullptr, и сохраняют копию… постусловия не обязательно являются исчерпывающими. Поэтому по каким-то неизвестным причинам он может разыменовать указатель.

Если да, то это будет UB. Так как же нам этого избежать?

Предлагаемое решение выглядит следующим образом:

class myostream :
    private std::streambuf,
    public std::ostream
{
public:
    myostream() : std::ostream{this} {}

    // other stuff...
};

Так здорово! Проблема решена, да?

Ну нет.

Потому что, видите ли, стандарт не говорит, что конструктор ostream или basic_ios::init() не удаляет переданный указатель.

basic_ios::init() может сделать это:

auto basic_ios::init(streambuf* p_buf)
{
    // do all the stuff init() is specified to do, and then...

    delete p_buf;
}

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

Или, может быть, он делает это:

auto basic_ios::init(streambuf* p_buf)
{
    // do all the stuff init() is specified to do, and then...

    p_buf->~streambuf();
    
    ::new (static_cast<void*>(p_buf)) streambuf{};
}

Опять же, почему бы и нет? Это не будет буквально противоречить точной формулировке контракта basic_ios::init(), как указано в стандарте. Так что это может случиться, верно?

Если вы предполагаете, что basic_ios::init() может делать с указателем все, что в нем явно не указано, то ваша хитрая стратегия наследования тоже не сработает. На самом деле… буквально ничего не получится. Если basic_ios::init() разрешено делать БУКВАЛЬНО ВСЁ с указателем, который вы ему передаете — при условии, что это не противоречит явной формулировке контракта — тогда вы не можете ничего предполагать относительно указателя на буфер потока, который вы ему передаете. Вы не можете предположить, что он не будет уничтожен. Вы не можете предположить, что он будет уничтожен. Вы не можете предположить, что он не будет перезаписан.

В общем, basic_ios::init() просто невозможно использовать безопасно. Это означает, что невозможно создавать собственные потоки вывода, потому что мы должны вызвать basic_ios::init() прямо или косвенно в какой-то момент (перед деструктором или любой функцией-членом).

Итак, вот ваш вывод. Просто невозможно создавать свои собственные потоки или буферы потоков, потому что авторы стандартов явно не исключают все возможные непредвиденные обстоятельства того, что может случиться с этим указателем.

Или… может быть… где-то наша логика сбилась с рельсов.

Послушайте, люди, пишущие стандарт, делают это не ради группы игроков D&D, которые разбирают микросемантику каждого отдельного пункта правил в поисках способа обмануть систему. У комитета нет ни времени, ни терпения, чтобы удовлетворить желание каждого абсурдного фанатика, искажающего правила, найти лазейки. Они будут включать в себя столько явных деталей, сколько необходимо разумным разработчикам для создания реализаций, которые ведут себя согласованно друг с другом, и с пониманием того, что разумные читатели стандарта будут интерпретировать его.

Так что давайте подойдем к этому как разумные люди.

Стандарт определяет, что basic_ios::init() делает с переданным указателем. В нем ничего не говорится о разыменовании указателя, нет даже ненормативного примечания, предполагающего, что это может иметь место.

Да, здесь явно не указано, что указатель не будет разыменован (или удален, или что-то еще). Но учтите следующее: как я уже отмечал в другом комментарии, libc++ Clang в основном делает то же, что и первый блок кода выше. Если бы существовала разумная интерпретация контракта basic_ios::init(), подразумевающая, что указатель может быть разыменован… разве кто-нибудь не заметил бы проблему примерно за десять лет, пока libc++ широко использовался? Вам не кажется, что, может быть, один или два дезинфицирующих средства могли это заметить?

И из любопытства я также проверил исходный код стандартной библиотеки Microsoft. Да, он делает то же самое: передает указатель на элемент данных буфера потока. Это две основные широко используемые стандартные библиотеки. Я не знаю, как долго используется эта конкретная стандартная библиотека, но опять же… не думаете ли вы, что кто-нибудь уже поднял бы этот вопрос, если бы была разумная интерпретация стандарта, согласно которой этот указатель буфера потока мог бы быть де- на который ссылаются до создания буфера потока?

(И сейчас я не могу откопать свою копию «Лангер и Крефт», но я почти уверен, что они делают то же самое.)

Еще раз: будьте разумны. IOstreams входит в стандарт с 1998 года, и даже до этого она была широко используемой библиотекой, начиная с 1984 года. Формулировка тщательно изучалась, пересматривалась и изучалась в десятках и десятках отчетов о дефектах. Если бы фраза «он не говорит, что не имеет значения» была бы разумной интерпретацией стандартного определения basic_ios::init()… не думаете ли вы, что кто-то сделал бы что-нибудь по этому поводу где-то за последние ~30–40 лет? Не думаете ли вы, что кто-то, работающий над стандартной библиотекой Microsoft или со стандартной библиотекой Clang, или один из многих, многих людей, которые создали свои собственные пользовательские потоки (включая людей, создающих новые стандартные пользовательские потоки, как в сетевых предложениях) — указал бы на проблему?

Будь благоразумен. В стандарте не обязательно явно указывать, что указатель не будет разыменовываться, потому что было бы чертовски глупо поступать с указателем, который вы не указали, который должен указывать на действительный буфер потока. Все остальное в basic_ios следует этому рассуждению: деструктор также не удаляет указатель. Действительно, если бы basic_ios::init() было разрешено разыменовывать указатель, это сильно усложнило бы процесс создания пользовательского потока. А для чего? Ради какой выгоды? Почему библиотека IOStreams была бы лучше, если бы она позволяла basic_ios::init() разыменовывать указатель потока? Как это можно сравнить со многими способами, которые были бы намного хуже, если бы вы не могли предположить, что безопасно передавать указатель на буфер потока-члена?

Вывод: тот факт, что в стандартной формулировке явно не указано… что то, что она явно делает с указателем буфера потока, — это единственное, что он делает с ним… не означает, что он может делать какие-то случайные действия с указателем буфера потока. . Особенно вещи, которые могли бы привести к необратимым последствиям, если бы они были сделаны неожиданно. Если бы ему требовался указатель на действительный буфер потока, он бы так и сказал. Это не так, и вместо этого перечисляет кучу вещей, которые не требуют действующего буфера потока.

Предложение: не относитесь к стандарту как к загадке и разбирайтесь в его формулировках в поисках ловушек.

ЯСНО, что намерение basic_ios::init() состоит в том, чтобы просто сравнить указатель с nullptr и сохранить копию. Нет никакого смысла не иметь этого значения и вместо этого требовать от разработчиков потоков прибегать к гимнастике, такой как множественное наследование (или динамическое выделение с последующим rdbuf() для последующего получения указателя или другие дурацкие, окольные идеи). Я имею в виду… почему? Зачем вам так спроектировать библиотеку? Это было бы абсурдно. Зачем вам так без необходимости ограничивать очевидный и самый безопасный способ реализации потока с базовым буфером потока?

tl;dr: 1) @JerryCoffin прав в том, что поведение определяется разумным следствием стандартной формулировки. 2) Первый блок кода в порядке, и вы можете передать указатель на неинициализированный буфер потока в basic_ios::init(). 3) Две основные стандартные библиотеки работают таким образом и делают это на протяжении десятилетий без каких-либо проблем. 4) В стандарте C++ нет риторических ловушек.

Я сочувствую фразе «пожалуйста, будьте разумны!» аргумент, но, насколько я понимаю, этот аргумент не работает в случае memset(nullptr, ..., 0): абсурдно, когда memset разыгрывает свой аргумент для нулевого счетчика (нет причин, и исторически код библиотеки предполагал, что это не так), но язык все равно требует действительный указатель. Фраза C++ «использование по назначению» находится в том же месте, где находится язык C, запрещающий memset(nullptr), поэтому он играет ту же роль. Если memset может быть придурком, почему бы не ostream?

Scott McPeak 23.05.2024 11:27

Во-первых, вы неверно истолковываете, что значит «быть разумным». Это не значит: «имейте сердце, я не могу на все ответить, так что будьте со мной добры». Это означает: «Если вы приходите с предположениями или сценариями, которые бросают вызов здравому смыслу, вы не можете ожидать разумных ответов». В этом случае, если вы выдвигаете предположения, которые бросают вызов всему остальному, что говорит или подразумевает стандарт о том, что делает basic_ios::init() и как его предполагается использовать… тогда вы не можете ожидать, что стандарт вас развлечет.

indi 23.05.2024 12:13

Другими словами: в целом дизайн IOStreams явно предполагает вызов basic_ios::init() в конструкторах производных классов потоков. И явно предполагается, что ему будет дан указатель на буфер потока, который использует поток. Так почему же для совершенно очевидного варианта использования должна быть совершенно ненужная ловушка? Это было бы абсурдно, и было бы еще более абсурдно оставить такую ​​опасную ловушку совершенно незамеченной. Стандарт не утруждает себя тратой слов на абсурдность. Поэтому... будьте разумны.

indi 23.05.2024 12:13

Во-вторых, да, вполне разумно предположить, что если вы вызовете memset() с нулевым размером, он никогда не будет загружать память. Так почему мы знаем, что это UB? Потому что стандарт прямо говорит об этом (насколько я помню, несколькими способами). Если бы этого не произошло, вы могли бы предположить, что это безопасно, потому что, опять же, это разумное предположение. Другими словами, memset() «может быть придурком» против разумных предположений, потому что стандарт устраняет необходимость в предположениях и прямо говорит, что это придурок. Также можно легко сказать, что basic_ios::init() — придурок; Это не.

indi 23.05.2024 12:14

C99 7.1.4p1 явно запрещает использование нулевых аргументов для библиотечных функций. Но, насколько мне известно, в C++ есть только res.on.arguments p1 , в котором говорится о «недопустимом значении» и «назначенном использовании». Таким образом, я думаю, что в C++ единственная причина, по которой memset может быть придурком, — это лицензия, предоставляемая расплывчато сформулированными res.on.arguments. (Или, может быть, можно было бы заявить, что cstring.syn p1 включает C99 7.1.4 по ссылке, но я думаю, что это преувеличение.)

Scott McPeak 23.05.2024 14:44
res.on.arguments — это требования к стандартной библиотеке C++. memset() является частью стандартной библиотеки C. Политики библиотеки стандарта C++ не применяются к стандартной библиотеке C. Стандарт C++ просто импортирует или «делает доступной» стандартную библиотеку C (с учетом нескольких небольших указанных изменений, например, добавления функций в namespace std)… он не определяет ее. Так что то, что говорит res.on.arguments в C++, не имеет значения; вам нужно прочитать стандарт C.
indi 23.05.2024 22:03

В стандарте C: Да, во всей библиотеке есть информация об отсутствии аргументов nullptr, но в самой спецификации memset() говорится об «объекте, на который указывает {аргумент указателя}». Если оставить в стороне запутанные правила C о времени жизни объекта, это, бесспорно, означает, что аргумент-указатель не может быть nullptr. Даже если «записывает в первые n байты объекта, на который указывает» подразумевается, что байты не будут записаны, если n равно нулю, поэтому указатель никогда не нужно разыменовывать… объект, на который он указывает, все равно должен существовать, потому что стандарт явно говорит об этом. .

indi 23.05.2024 22:03

Хорошо, вы убедили меня, что memset не зависит от res.on.arguments для своего nullptr запрета, что снижает подразумеваемую силу res.on.arguments. Я все еще не убежден, что этого достаточно, чтобы сказать, что init, который разыменовывает, не соответствует, но он устраняет одно препятствие на пути к такому выводу. Однако в этом контексте, я думаю, ответ Криса сделал этот вопрос спорным.

Scott McPeak 24.05.2024 01:43

Я принял ответ Криса, но, по моему мнению, вопрос о том, разрешено ли std::basic_ios::init разыменовывать свой аргумент, остается открытым. Ваш ответ и комментарий расширили мое понимание этой темы (+1), даже если я не согласен со всеми высказанными замечаниями; Спасибо. Немного жаль, что этот аспект не имеет значения в данном контексте. Я мог бы задать дополнительный вопрос, если смогу придумать хороший способ сделать это; если я это сделаю, я позвоню тебе.

Scott McPeak 24.05.2024 16:24

Другие вопросы по теме

Является ли рекурсивный вызов main из его собственных параметров (злоупотребление sizeof с помощью VLA) стандартом C99?
Перегрузка и нестабильность
До C++11 «правило одного определения» нарушалось при инициализации членов класса нестатических и неконстантных переменных. Почему?
Что произойдет, если я вызову allocate_at_least(0) согласно стандарту C++23?
Почему изменяемая лямбда преобразуется в указатель на функцию вместо вызова оператора()?
Могут ли `::` и `*`, образующие тип указателя-члена, происходить из разных расширений макроса или они должны появляться как один токен?
Определено ли вычисление смещений указателей байтов между элементами композиции, не являющимися массивами?
Как проверить, является ли конструктор явно дефолтным
Требуется ли создание экземпляра для неиспользуемого, но инициализированного статического элемента данных const int шаблона класса?
Когда типы замыканий наконец стали структурными типами?