Требуются ли в C++26 реализации «инициализировать» неинициализированные переменные некоторым фиксированному шаблону байтов?

В C++26 чтение неинициализированных переменных больше не является неопределенным, теперь оно «ошибочное» (Что такое ошибочное поведение? Чем оно отличается от неопределенного поведения?).

Однако меня смущает формулировка:

[basic.indet]/1.2

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

(bold mine)

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

Верна ли моя интерпретация? Вынуждены ли реализации теперь перезаписывать неинициализированные переменные?

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

Martin Brown 25.07.2024 12:11

Согласно предложению C++26, реализации также будет разрешено диагностировать ошибочное поведение — либо во время трансляции, либо во время выполнения программы. Большинство современных компиляторов уже могут выдавать предупреждения об использовании неинициализированных переменных (хотя по историческим причинам эти предупреждения обычно отключены по умолчанию), поэтому, если это будет одобрено в C++26, я ожидаю, что большинство компиляторов просто выдадут диагностику, если осуществляется доступ к значению неинициализированной переменной.

Peter 25.07.2024 12:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
2
184
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Связанный P2795R5 говорит в разделе «Последствия для производительности и безопасности»:

  • Автоматическое хранилище для автоматической переменной всегда полностью инициализируется, что потенциально может повлиять на производительность. P2723R1 подробно описывает затраты. Обратите внимание, что эта стоимость применяется даже тогда, когда создается переменная типа класса, не имеющая заполнения и чей конструктор по умолчанию инициализирует все члены.
  • В частности, объединения полностью инициализируются. ...

Он также указывает, что, хотя автоматические локальные переменные могут быть аннотированы [[indeterminate]] для подавления этой инициализации, нет способа избежать этого для каких-либо временных объектов.

Так что похоже ваша интерпретация верна.

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


Изменить - почему я говорю, что не имеет значения, каково магическое значение и даже происходит ли эта инициализация на самом деле?

  1. Мотивация состоит в том, чтобы прекратить оценку (т. е. преобразование glvalue в prvalue) неинициализированных автоматических переменных, имеющих неопределенное поведение. Вместо этого это будет ошибочное поведение, которое рекомендуется диагностировать в реализациях.

    • Если реализация не диагностирует ошибочное поведение, результат оценки действителен.
  2. Вышеупомянутое не может зависеть от конкретного битового шаблона, если этот битовый шаблон когда-либо может быть создан с помощью допустимого выражения без риска ошибочной диагностики.

    • Ни один обычный примитив не имеет таких волшебных битовых комбинаций, за исключением теперь редкого представления ловушки.

    • например. вы не можете использовать ни тихий, ни сигнальный NaN для обозначения ошибочных значений, потому что если

      double fine = std::numeric_limits<double>::quiet_NaN;
      double errn;
      
      std::isnan(fine); // not erroneous
      std::isnan(errn); // erroneous behaviour
      

      необходимо обрабатывать оба значения по-разному, оно не может основываться на битовом шаблоне.

    • То же самое тривиально верно и для целочисленных типов, и в любом случае [basic.indet/2] говорит

      За исключением следующих случаев... если в результате оценки получено ошибочное значение, поведение является ошибочным, и результатом оценки является полученное таким образом значение, но не ошибочное.

      где все исключения относятся к «беззнаковому обычному типу символов» и std::byte, поэтому:

      int errn;      // erroneous value
      foo(errn ^ 0); // 1, 2
      foo(errn);     // 3
      
      1. XOR имеет ошибочное поведение, но если его не диагностировать, он должен выдать безошибочное значение с точно такой же битовой комбинацией, что и ошибочный ввод.
      2. вызов foo с неошибочным значением не должен диагностироваться
      3. вызов foo с точно таким же битовым шаблоном может быть диагностирован
  3. Если единственной целью является предотвращение утечки неинициализированных (автоматических) переменных в UB, достаточно требовать такого рода инициализации только для типов с представлениями-ловушками.

    Также может потребоваться отключить (или защитить с помощью диагностических проверок) некоторые оптимизации, ранее разрешенные UB, но это не является ни необходимым, ни достаточным, чтобы это зависело от конкретного битового шаблона.

«или даже действительно ли эта инициализация происходит» Хм, вы хотите сказать, что формулировка выглядит так, будто она не требует принудительной инициализации? Потому что для меня «независимо от состояния программы» звучит так, будто вы не можете сохранить существующее значение.

HolyBlackCat 25.07.2024 12:57

Согласен, но, похоже, это не требует какого-либо поведения, которое на самом деле зависит от значения. Там определенно говорится, что это должно произойти, но далеко не ясно, почему.

Useless 25.07.2024 13:58

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

HolyBlackCat 27.07.2024 06:01

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

Useless 27.07.2024 09:07

Я могу предположить некоторые возможные оправдания. Это может предотвратить определенные уязвимости, связанные с предварительным заполнением неинициализированной переменной определенными значениями. Это также может помочь в отладке (даже если нет автоматического отчета, если вы видите 0xBEBEBEBE (или что-то еще) в переменной, это сразу же говорит вам, что что-то не так). Он также устраняет случайное поведение при неинициализированном чтении, делая вызванные ими ошибки воспроизводимыми.

HolyBlackCat 27.07.2024 10:50

Это зависит от того, что вы подразумеваете под «некоторым фиксированным байтовым шаблоном».

Реализации могут выбирать значения, зависящие от типа переменной, поскольку это статическое свойство программы, не зависящее от ее состояния. В случаях, когда вы заявляете, например. выровненный буфер unsigned char или std::byte и последующее размещение в них новых объектов, обратите внимание, что это первый шаг, получение хранилища, которое записывает в память ошибочные значения; новый шаг размещения будет пустым, если он выполняет тривиальную инициализацию по умолчанию. Но компилятор может выполнить некоторый статический анализ, чтобы увидеть, какой тип памяти вы собираетесь предоставить для использования буфера, и соответствующим образом выбрать ошибочные значения.

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

Обратите внимание, что начальные ошибочные значения не применяются к объектам со статической или потоковой продолжительностью хранения (которые инициализируются нулем) или динамической продолжительностью хранения. Также можно отказаться, используя атрибут [[indeterminate]].

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