Является ли переменная массив размером 1?

Рассмотрим это:

int main(int, char **) {
  int variable = 21;
  int array[1] = {21};
  using ArrayOf1Int = int[1];
  (*reinterpret_cast<ArrayOf1Int *>(&variable))[0] = 42;
  *reinterpret_cast<int *>(&array) = 42;
  return 0;
}

Я только нарушил правило строгого псевдонимов?

Или, как в этом комментарии, это привело меня к этому вопросу: Является ли переменная массивом размера 1?

Обратите внимание, что я отметил это как вопрос о языке-адвокате. Таким образом, меня не интересует -fno-strict-aliasing или специфическое поведение компилятора, но вместо этого в том, что сказано в стандарте. Также я думаю, было бы интересно узнать, как и как это изменилось между версиями С++ 03, С++ 11, С++ 14 и более поздними версиями.

Ответ 1

Очевидно, что если объект был переменной массива размера один, вы можете инициализировать ссылку на массив размером один с объектом:

int variable{21};
int (&array)[1] = variable; // illegal

Однако инициализация является незаконной. Соответствующим пунктом для этого является пункт 4 [conv] (стандартные преобразования), который указан в пункте 1:

Стандартные преобразования - это неявные преобразования со встроенным значением. В пункте 4 перечисляется полный набор таких преобразований.

Этот раздел слишком длинный, чтобы процитировать здесь, но нечего сказать о преобразовании объекта в ссылку массива любого размера. Аналогично, в разделе reinterpret_cast (5.2.10 [expr.reinterpret.cast]) не указано какое-либо поведение, связанное с массивами, но излагает это исключение в параграфе 1:

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

Я не думаю, что существует явное утверждение о том, что объект не является массивом одного объекта, но есть достаточные упущения, чтобы сделать случай неявным. Гарантия, предоставляемая стандартными связанными объектами и массивом, заключается в том, что указатель на объект ведет себя так, как если бы они указывали на массив размера 1 (5.7 [expr.add], пункт 4):

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

Наличие этого утверждения также подразумевает, что объекты массива и объекты nonarray являются разными объектами: если они считались одинаковыми, это утверждение не было бы необходимо для начала.

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

Ответ 2

reinterpret_cast ведет себя предсказуемо только в С++ 11 и выше, поэтому ни одна из строк не имеет гарантированного определения поведения до С++ 11. Мы перейдем к предположению С++ 11 или выше.

Первая строка

(*reinterpret_cast<decltype(&array)>(&variable))[0] = 42;

В этой строке разыменование reinterpret_cast дает значение glvalue, но не получает доступ к объекту int через это значение glvalue. К моменту обращения к объекту int glvalue, относящийся к массиву, уже был затуманен указателем на этот объект (то есть int*).

Однако можно "придумать" случай, который выглядит так, будто он может содержать строгое нарушение псевдонимов, например:

struct S {
    int a[1];
};
int variable = 42;
S s = reinterpret_cast<S&>(variable);

Это не нарушает строгий псевдоним, потому что вам разрешен доступ к объекту через подобъект типа агрегата или объединения. (Это правило существовало с С++ 98.)

Вторая строка

*reinterpret_cast<decltype(&variable)>(&array) = 42;

reinterpret_cast гарантированно даст указатель на первый подобъект массива, который является объектом int, поэтому его назначение с помощью указателя int четко определено.

Ответ 3

В одном недавнем проекте говорится:

§ [expr.unary.op]/3:

Результат унарного и оператора - указатель на его операнд. [...] Для целей арифметики указателя (5.7) и сравнения (5.9, 5.10) объект, который не является элементом массива, адрес которого берется таким образом, считается принадлежащим массиву с одним элементом типа T.

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

Что касается изменений между версиями: эта формулировка находится в N4296 (черновик между С++ 14 и С++ 17), но не N4140 или N3337 (в основном С++ 14 и С++ 11 соответственно).

Стандарт C11 имеет смутно похожий язык для fscanf_s и fwscanf_s (§K.3.5.3.2/4):

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

Ответ 4

Массив из 1 целых чисел не совместим с макетами с целым числом.

Это означает:

struct A {
  int x;
};
struct B {
  int y[1];
};
A a={0};
std::cout << ((B*)&a).y[0];

не определено поведение. См. [basic.types]/11 для определения совместимости с макетами.

A::x и B::y не являются теми же типами из [basic.types]/10 - один находится под [basic.types]/10.2 (скалярный тип), а другой под [basic.types]/10.4 (массив литералов). Они не являются совместимыми с макетами перечислениями. Они не являются типами классов, поэтому [class.name]/20-21 не применяется.

Таким образом, [class.name]/20 (общая начальная последовательность) не рассматривает x и y как общую начальную последовательность.

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

Я лично подумал бы, что было бы неплохо утверждать, что массив T[N] совместим с макетами с последовательностью N смежных T s. Это позволило бы использовать ряд полезных методов, таких как:

struct pixel {
  union {
    struct {
      char r, g, b, a;
    } e;
    std::array<char,4> pel;
  };
};

где pixel.pel[0] гарантированно соответствует pixel.e.r. Но, насколько мне известно, это не законно.