Копирование структур с неинициализированными членами - PullRequest
29 голосов
/ 07 февраля 2020

Допустимо ли копировать структуру, некоторые из членов которой не инициализированы?

Я подозреваю, что это неопределенное поведение, но если это так, это оставляет любые неинициализированные члены в структуре (даже если эти члены являются никогда не использовал напрямую) довольно опасно. Поэтому мне интересно, есть ли что-то в стандарте, что позволяет это.

Например, это действительно?

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

Ответы [ 4 ]

23 голосов
/ 07 февраля 2020

Да, если неинициализированный элемент не является узким типом символов без знака или std::byte, то копирование структуры, содержащей это неопределенное значение, с помощью неявно определенного конструктора копирования является технически неопределенным поведением, так как оно предназначено для копирования переменной с неопределенным значением из-за [dcl.init] / 12 .

Это применимо здесь, потому что неявно сгенерированный конструктор копирования, за исключением union s, определен для копирования каждого член по отдельности, как при прямой инициализации, см. [class.copy.ctor] / 4 .

Это также является предметом активной CWG проблемы 2264 .

Полагаю, на практике у вас не возникнет никаких проблем с этим.

Если вы хотите быть на 100% уверенным, использование std::memcpy всегда имеет четко определенное поведение, если тип тривиально копируемое , даже если члены имеют неопределенное значение.


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

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}
11 голосов
/ 07 февраля 2020

Как правило, копирование неинициализированных данных является неопределенным поведением, поскольку эти данные могут находиться в состоянии захвата. Цитирование this page:

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

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

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

0 голосов
/ 12 февраля 2020

Поскольку все члены Data имеют примитивные типы, data2 получит точную "побитовую копию" всех членов data. Таким образом, значение data2.b будет точно таким же, как значение data.b. Однако точное значение data.b невозможно предсказать, поскольку вы не инициализировали его явно. Это будет зависеть от значений байтов в области памяти, выделенной для data.

0 голосов
/ 10 февраля 2020

В некоторых случаях, таких как описанный, стандарт C ++ позволяет компиляторам обрабатывать конструкции любым способом, который их клиенты считают наиболее полезным, не требуя, чтобы поведение было предсказуемым. Другими словами, такие конструкции вызывают «неопределенное поведение». Однако это не означает, что такие конструкции должны быть «запрещены», поскольку стандарт C ++ явно отказывается от юрисдикции в отношении того, что «правильно» выполненным программам разрешено делать. Хотя мне неизвестно о каком-либо опубликованном документе Rationale для стандарта C ++, тот факт, что он описывает Undefined Behavior во многом аналогично тому, как это делает C89, предполагает, что предполагаемое значение аналогично: «Неопределенное поведение дает разработчику лицензию, позволяющую не отлавливать определенные программные ошибки, которые являются сложными. для диагностики. Он также определяет области возможного соответствующего расширения языка: разработчик может расширить язык, предоставив определение официально неопределенного поведения ".

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

Кроме того, в некоторых ситуациях наиболее эффективным может быть поведение неинициализированных данных в недетерминированность c мода. Например, если:

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

, если нижестоящий код не будет заботиться о значениях каких-либо элементов x.dat или y.dat, индексы которых не указаны в arr, код может быть оптимизировано для:

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

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

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

Из того, что я могу сказать, Стандарт C ++ не пытается сказать, что какое-либо из этих поведений является достаточно более полезным, чем другое, так как чтобы оправдать его обязательство. По иронии судьбы, это отсутствие спецификации может быть направлено на облегчение оптимизации, но если программисты не смогут использовать какие-либо слабые поведенческие гарантии, любая оптимизация будет отменена.

...