Допустимо ли копировать структуру, некоторые члены которой не инициализированы?
Я подозреваю, что это неопределенное поведение, но если это так, то оставлять неинициализированные члены в структуре (даже если эти члены никогда не используются напрямую) довольно опасно. Поэтому мне интересно, есть ли что-то в стандарте, что позволяет это.
Например, это действительно?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
c++
initialization
copy-constructor
undefined-behavior
Томек Цайка
источник
источник
Ответы:
Да, если неинициализированный член не является беззнаковым узким символьным типом или
std::byte
, то копирование структуры, содержащей это неопределенное значение, с помощью неявно определенного конструктора копирования является технически неопределенным поведением, так как оно предназначено для копирования переменной с неопределенным значением того же типа, поскольку из [dcl.init] / 12 .Это применимо здесь, потому что неявно сгенерированный конструктор копирования определен, за исключением
union
s, для копирования каждого члена индивидуально, как если бы это было с помощью прямой инициализации, см. [Class.copy.ctor] / 4 .Это также является предметом активной проблемы CWG 2264 .
Полагаю, на практике у вас не возникнет никаких проблем с этим.
Если вы хотите быть на 100% уверены, использование
std::memcpy
всегда имеет четко определенное поведение, если тип легко копируется , даже если члены имеют неопределенное значение.Помимо этих проблем, вы всегда должны правильно инициализировать членов вашего класса указанным значением при построении, если вы не требуете, чтобы класс имел тривиальный конструктор по умолчанию . Вы можете легко сделать это, используя синтаксис инициализатора элементов по умолчанию, например, для инициализации значений членов:
источник
memcpy
, даже для тривиально копируемых типов. Единственным исключением являются союзы, для которых неявный конструктор копирования действительно копирует представление объекта, как если быmemcpy
.Как правило, копирование неинициализированных данных является неопределенным поведением, поскольку эти данные могут находиться в состоянии захвата. Цитирую эту страницу:
Сигнальные NaN возможны для типов с плавающей запятой, и на некоторых платформах целые числа могут иметь представления ловушек.
Тем не менее, для тривиально копируемых типов можно использовать
memcpy
для копирования необработанное представление объекта. Это безопасно, поскольку значение объекта не интерпретируется, и вместо этого копируется необработанная последовательность байтов представления объекта.источник
unsigned char[64]
)? Обработка байтов структуры как имеющих неопределенные значения может излишне затруднить оптимизацию, но требование, чтобы программисты вручную заполняли массив бесполезными значениями, еще больше снизило бы эффективность.В некоторых случаях, таких как описанный, стандарт C ++ позволяет компиляторам обрабатывать конструкции любым способом, который их клиенты считают наиболее полезным, не требуя, чтобы поведение было предсказуемым. Другими словами, такие конструкции вызывают «неопределенное поведение». Однако это не означает, что такие конструкции должны быть «запрещены», поскольку стандарт C ++ явно отказывается от юрисдикции в отношении того, что «правильно» выполненным программам разрешено делать. Хотя я не знаю ни одного опубликованного документа Rationale для стандарта C ++, тот факт, что он описывает Undefined Behavior во многом подобно тому, как это делает C89, наводит на мысль, что предполагаемое значение похоже: «Неопределенное поведение дает разработчику лицензию, чтобы не перехватывать определенные программные ошибки, которые являются сложными. чтобы диагностировать.
Существует много ситуаций, когда наиболее эффективный способ обработки чего-либо включает в себя написание частей структуры, которые будут заботиться о нижестоящем коде, и в то же время исключать те, которые не будут заботиться о нижестоящем коде. Требование, чтобы программы инициализировали всех членов структуры, включая тех, которые ни о чем не заботятся, без необходимости мешало бы эффективности.
Кроме того, в некоторых ситуациях может оказаться наиболее эффективным, чтобы неинициализированные данные вели себя недетерминированным образом. Например, учитывая:
если нижестоящий код не будет заботиться о значениях каких-либо элементов
x.dat
илиy.dat
чьи индексы не были перечисленыarr
, код может быть оптимизирован для:Это повышение эффективности не было бы возможным, если бы программистам требовалось явно писать каждый элемент
temp.dat
, включая те, которые не интересуют нижестоящие, до его копирования.С другой стороны, есть некоторые приложения, в которых важно избежать возможности утечки данных. В таких приложениях может быть полезно иметь версию кода, которая оснащена инструментами для перехвата любой попытки скопировать неинициализированное хранилище, не обращая внимания на то, будет ли на него смотреть нисходящий код, или было бы полезно иметь гарантию реализации, что любое хранилище чье содержимое может быть пропущено, обнуляется или иным образом перезаписывается неконфиденциальными данными.
Из того, что я могу сказать, Стандарт C ++ не пытается сказать, что любое из этих поведений является достаточно более полезным, чем другое, чтобы оправдать его обязательство. По иронии судьбы, это отсутствие спецификации может быть направлено на облегчение оптимизации, но если программисты не могут использовать какие-либо слабые поведенческие гарантии, любая оптимизация будет сведена на нет.
источник
Поскольку все члены
Data
имеют примитивные типы,data2
они получат точную «побитовую копию» всех членовdata
. Таким образом, значениеdata2.b
будет точно таким же, как значениеdata.b
. Однако точное значениеdata.b
невозможно предсказать, поскольку вы не инициализировали его явно. Это будет зависеть от значений байтов в области памяти, выделенной дляdata
.источник
std::memcpy
. Ничто из этого не мешает использоватьstd::memcpy
илиstd::memmove
. Это только предотвращает использование неявного конструктора копирования.