Является ли переменная массив размером 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
. Но, насколько мне известно, это не законно.