Является ли sizeof (* ptr) неопределенным поведением при указании на недопустимую память?

#c #c #language-lawyer

#c #c #язык-юрист

Вопрос:

Все мы знаем, что разыменование нулевого указателя или указателя на нераспределенную память вызывает неопределенное поведение.

Но каково правило при использовании в переданном выражении sizeof ?

Например:

 int *ptr = 0;
int size = sizeof(*ptr);
  

Это тоже не определено?

Комментарии:

1. Разные языки имеют разные правила. Нет «правила», пока вы не удалите все языковые теги, кроме одного.

2. @n.m. Это может быть случай «что сделано, то сделано». Я не думаю, что добавление другого ответа, адаптированного к различным стандартам C , повредит здесь.

3. @YSC принятый ответ дает не a , а окончательную норму.

4. YSC, я не уверен, что еще, по вашему мнению, можно добавить к моему ответу. Стандарт ISO очень четко определяет правильное поведение sizeof , нет возможности для изменения. Тот факт, что ваш другой вопрос, который был закрыт как обман этого, был необходим из-за ошибки в одной конкретной реализации C , никоим образом не уменьшает явную гарантию стандарта. Если вы хотите процитировать что-то в стандарте , которое, по вашему мнению, сводит на нет мои ответы, обязательно сделайте это, и я обновлю его, если потребуется.

5. @YSC Вы пропустили ответ paxdiablo? В нем специально указана стандартная цитата «не вычисляется».

Ответ №1:

В большинстве случаев вы обнаружите, что sizeof(*x) это вообще не вычисляется *x . И, поскольку это оценка (удаление ссылок) указателя, который вызывает неопределенное поведение, вы обнаружите, что в основном все в порядке. Стандарт C11 говорит об этом в 6.5.3.4. The sizeof operator /2 (мой акцент во всех этих кавычках):

sizeof Оператор выдает размер (в байтах) своего операнда, который может быть выражением или именем типа, заключенным в скобки. Размер определяется по типу операнда. Результатом является целое число. Если тип операнда является типом массива переменной длины, вычисляется операнд; в противном случае операнд не вычисляется, и результатом является целочисленная константа.

Это идентичная формулировка для того же раздела в C99. У C89 была немного другая формулировка, потому что, конечно, на тот момент не было VLA. От 3.3.3.4. The sizeof operator :

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

Итак, в C для всех не-VLA не происходит разыменования, и оператор четко определен. Если тип *x является VLA, это считается фазой выполнения sizeof , что-то, что необходимо проработать во время выполнения кода — все остальные могут быть вычислены во время компиляции. Если x сам по себе является VLA, это то же самое, что и в других случаях, при использовании *x в качестве аргумента оценка не выполняется sizeof() .


C имеет (как и ожидалось, поскольку это другой язык) немного другие правила, как показано в различных итерациях стандарта:

Первый, C 03 5.3.3. Sizeof /1 :

sizeof Оператор выдает количество байтов в объектном представлении своего операнда. Операндом является либо выражение, которое не вычисляется, либо заключенный в скобки идентификатор типа.

В, C 11 5.3.3. Sizeof /1 , вы найдете немного другую формулировку, но тот же эффект:

sizeof Оператор выдает количество байтов в объектном представлении своего операнда. Операндом является либо выражение, которое является недооцененным операндом (пункт 5), либо заключенный в скобки идентификатор типа.

C 11 5. Expressions /7 (вышеупомянутый пункт 5) определяет термин «недооцененный операнд» как, возможно, одну из самых бесполезных, избыточных фраз, которые я читал некоторое время, но я не знаю, что происходило в голове людей ISO, когда они его писали:

В некоторых контекстах ([некоторые ссылки на разделы, подробно описывающие эти контексты — pax]) появляются недооцененные операнды. Недооцененный операнд не вычисляется.

C 14/17 имеет ту же формулировку, что и C 11, но не обязательно в тех же разделах, поскольку материал был добавлен перед соответствующими частями. Они находятся в 5.3.3. Sizeof /1 и 5. Expressions /8 для C 14 и 8.3.3. Sizeof /1 и 8. Expressions /8 для C 17.

Итак, в C вычисление *x in sizeof(*x) никогда не выполняется, поэтому оно четко определено, при условии, что вы следуете всем другим правилам, таким как предоставление полного типа, например. Но суть в том, что разыменование не выполняется, что означает, что это не вызывает проблем.

На самом деле вы можете увидеть это отсутствие оценки в следующей программе:

 #include <iostream>
#include <cmath>

int main() {
    int x = 42;
    std::cout << x << 'n';

    std::cout << sizeof(x = 6) << 'n';
    std::cout << sizeof(x  ) << 'n';
    std::cout << sizeof(x = 15 * x * x   7 * x - 12) << 'n';
    std::cout << sizeof(x  = sqrt(4.0)) << 'n';

    std::cout << x << 'n';
}
  

Вы можете подумать, что последняя строка будет выводить что-то, сильно отличающееся от 42 ( 774 , основываясь на моих грубых расчетах), потому x что было немного изменено. Но на самом деле это не так, поскольку здесь имеет значение только тип выражения in sizeof , а тип сводится к любому типу x .

Что вы видите (кроме возможности разных размеров указателей на строках, отличных от первой и последней), так это:

 42
4
4
4
4
42
  

Комментарии:

1. Для полноты картины, в C (который не имеет VLAS) выражение никогда не вычисляется: C 11, 5.3.3 говорит: «Операнд является либо выражением, которое является недооцененным операндом (пункт 5), либо заключенным в скобки идентификатором типа».

2. Приветствую, @Mike, включил это в ответ для полноты.

3. @paxdiablo: может быть важно явно добавить, что перенос нулевого указателя является допустимым кодом C . При вычислении он вызывает UB, но компилируется просто отлично. YSC может не знать об этом.

4. Почему не *x приводит к UB, когда не оценивается?

5. @xskxzr По той же причине, которая 2 2 не приводит к 4 тому, что не вычисляется . Честно говоря, кажется, что некоторые просто полностью игнорируют слова «не оценено»

Ответ №2:

Нет. sizeof является оператором и работает с типами, а не с фактическим значением (которое не вычисляется).

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

 int* ptr = 0;
size_t size = sizeof *ptr;
size = sizeof (int);   /* brackets still required when naming a type */
  

Комментарии:

1. Это, и выражение никогда не вычисляется — оно анализируется только для определения типа результата. Для чего это стоит, я думаю amp;*ptr , это тоже законно.

2. @ChrisLutz: Спасибо, я включил «не оценено» в свой ответ.

3. Почему важно помнить, что это оператор?

Ответ №3:

Ответ вполне может быть другим для C, где sizeof не обязательно является конструкцией времени компиляции, но в C выражение, предоставленное для sizeof , никогда не вычисляется. Таким образом, никогда не существует возможности для проявления неопределенного поведения. По аналогичной логике вы также можете «вызывать» функции, которые никогда не определены [поскольку функция фактически никогда не вызывается, определение не требуется], факт, который часто используется в правилах SFINAE.

Ответ №4:

sizeof и decltype не оценивайте их операнды, только типы вычислений.

Ответ №5:

sizeof(*ptr) то же sizeof(int) самое, что и в этом случае.

Комментарии:

1. ptr — это указатель на целое число. * ptr — целое число. sizeof(* ptr) — это sizeof(целое число).

Ответ №6:

Поскольку sizeof не вычисляет свой операнд (за исключением случаев массивов переменной длины, если вы используете C99 или более позднюю версию), в выражении sizeof (*ptr) ptr не вычисляется, поэтому он не разыменовывается. Оператору sizeof нужно только определить тип выражения *ptr , чтобы получить соответствующий размер.