Не оценивает ли выражение, к которому применяется значение sizeof, делает его законным для разыменования нулевого или недопустимого указателя внутри sizeof в С++?

Прежде всего, я видел этот вопрос о C99, а принятый операнд ссылок на ответы не оценивается в стандартном черновике C99. Я не уверен, что этот ответ относится к С++ 03. Там также этот вопрос о С++, который имеет принятый ответ, ссылаясь на аналогичную формулировку, а также в некоторых контекстах появляются неоцененные операнды. Неопределенный операнд не оценивается. формулировка.

У меня есть этот код:

 int* ptr = 0;
 void* buffer = malloc( 10 * sizeof( *ptr ) );

Вопрос: есть ли разворот нулевого указателя (и, следовательно, UB) внутри sizeof()?

С++ 03 5.3.3/1 говорит Оператор sizeof дает количество байтов в представлении объекта его операнда. Операндом является либо выражение, которое не оценивается, либо идентификатор типа в скобках.

Связанные с ответами цитируют эту или аналогичную формулировку и используют часть "не оценивается", чтобы вывести там UB.

Однако я не могу найти, где именно стандартная ссылка оценивает наличие или отсутствие UB в этом случае.

"Не оценивает" выражение, к которому применяется sizeof, делает его законным для разыменования нулевого или недопустимого указателя внутри sizeof в С++?

Ответ 1

Я считаю, что это в настоящее время не указано в стандарте, как и многие вопросы, такие как Какова категория значений операндов операторов С++ при неопределенной?. Я не думаю, что это было преднамеренно, как, например, hvd points outs, это, вероятно, очевидно для комитета.

В этом конкретном случае я думаю, что у нас есть доказательства, чтобы показать, что это за намерение. Из GB 91 комментарий из собрания Rapperswil, в котором говорится:

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

и предложил альтернативное выражение, оно относится к этому выражению, которое уже не соответствует стандарту, но может быть найдено в N3090:

noexcept(*(U*)0 = declval<U>())

Предложение отклонено, поскольку оно не вызывает поведение undefined, поскольку оно не оценено:

Не существует поведения undefined, потому что выражение является неоцененным операндом. Не совсем ясно, что предлагаемое изменение будет более ясным.

Это обоснование относится и к sizeof, так как оно не оценивается.

Я говорю underspecified, но мне интересно, распространяется ли это на раздел 4.1 [conv.lval], который гласит:

Значение, содержащееся в объекте, обозначенном lvalue, является результатом rvalue. Когда происходит преобразование lvalue-to-rvalue в пределах операнда sizeof (5.3.3) значение, содержащееся в ссылочном объекте, не доступно, поскольку этот оператор не оценивает его операнд.

В нем говорится, что доступное значение не доступно, что, если мы следуем логике issue 232, означает, что поведение undefined отсутствует:

Другими словами, это всего лишь акт "выборки", преобразования lvalue-to-rvalue, который вызывает неправильное поведение или undefined поведение

Это несколько умозрительно, так как проблема еще не решена.

Ответ 2

В спецификации указано, что разыменование какого-либо указателя, который является NULL, является UB. Поскольку sizeof() не является реальной функцией и фактически не использует аргументы ни для чего другого, кроме типа, он никогда не ссылается на указатель. Это ПОЧЕМУ это работает. Кто-то еще может получить все точки для поиска формулировки спецификации, которая гласит, что "аргумент sizeof не ссылается".

Обратите внимание, что также вполне законно делать int arr[2]; size_t s = sizeof(arr[-111100000]); тоже - неважно, что такое индекс, потому что sizeof никогда не "ничего делать" с переданным аргументом.

Еще один пример, показывающий, как он "ничего не делает", будет примерно таким:

int func()
{
    int *ptr = reinterpret_cast<int*>(32);
    *ptr = 7;
    return 42;
}

size_t size = sizeof(func()); 

Опять же, это не сработает, потому что func() просто решается компилятором по типу, который он создает.

В равной степени, если sizeof фактически "что-то" делает с аргументом, что произойдет, когда вы это сделаете:

   char *buffer = new sizeof(char[10000000000]);

Будет ли он создавать распределение стека 10000000000, а затем вернуть размер после того, как он разбил код, потому что не хватает мегабайт стека? [В некоторых системах размер стека подсчитывается в байтах, а не в мегабайтах]. И пока никто не пишет такой код, вы можете легко найти что-то подобное, используя typedef либо buffer_type как массив char, либо какой-то struct с большим контентом.

Ответ 3

Поскольку вы явно запрашивали стандартные ссылки - [expr.sizeof]/1:

Операндом является либо выражение, которое является неоцененным операндом (Раздел 5) или идентификатор типа в скобках.

[выражение]/8:

В некоторых контекстах отображаются неоцененные операнды (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). Неопределенный операнд не оценивается.

Поскольку выражение (т.е. разделение) никогда не оценивается, это выражение не подлежит некоторым ограничениям, которые он обычно нарушает. Только тип проверяется. Фактически, стандарт использует нулевые ссылки сам в примере в [dcl.fct]/12:

Тип возвращаемого возврата наиболее полезен для типа, который будет больше сложно определить до описатель-ID:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);

а не

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

- примечание к концу]