Начиная со стандарта
C++17, была изменена семантика таких операторов, как, например, операторы сдвига (
shift operators) и оператор индексирования (
subscript operator). Вычисления их операндов стало детерминированным: сначала вычисляется первый операнд, и все его побочные эффекты применяются, а затем второй операнд.
В стандартах
C++17 и
C++20 оператор индексирования определяется следующим образом:
postfix-expression [ expr-or-braced-init-list ]
В стандарте
C++23 он определяется как
postfix-expression [ expression-listopt ]
Условно выражение с оператором индексирования можно представить в виде
E1[E2]. Это выражение вычисляется как
*((E1)+(E2)).
И, начиная со стандарта
C++17, говорится, что
цитата: |
The expression E1 is sequenced before the expression E2. |
|
Например, если имеется массив
T a[N];
где
T - это некоторый тип элементов массива, а
N - константное выражение, задающее количество элементов в массиве, то к его
i-ому элементу можно обратиться как
a
Так как оператор суммирования
+ в выражении
*((E1)+(E2)) является коммутативным, то выше приведенное выражение обращения к
i-ому элементу массива можно также записать как
i[a]
или как
( i )[a]
В этом случае операнд
E1 представляет из себя
i или
( i ), и операнд
E2:
a.
Итак, сначала вычисляется операнд
E1, и применяются все его побочные эффекты, и только затем операнд
E2.
Однако компилятор
MS VS-22 C++ ошибочно вычисляет сначала выражение в квадратных скобках, а лишь затем выражение перед квадратными скобками, если массив или указатель на элемент массива поместить в квадратные скобки вместо индексного выражения.
Ниже приведена демонстрационная программа, которая иллюстрирует описанный баг компилятора.
#include <iostream>
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
for (const int *p = a; p != a + sizeof( a ) / sizeof( *a ); ++p)
{
std::cout << ( *p++ - 1 )[( --p, a )] << ' ';
}
std::cout << '\n';
}
или в соответствии с новыми стандартами C++
import std;
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
for (const int *p = a; p != a + std::size( a ); ++p)
{
std::cout << ( *p++ - 1 )[( --p, a )] << ' ';
}
std::cout << '\n';
}
Это корректная программа, которая должна вывести на консоль все элементы массива, не смотря на то, что обращение к элементам массива переусложнено (ради демонстрации бага компилятора).
Однако при использовании компилятора
MS VS-22 C++ данная программа завершается при выполнении аварийно, так как при первой итерации цикла сначала вычисляется выражение в квадратных скобках
[( --p, a )], в результате чего указатель
p после декримента указывает за пределы массива, и происходит обращение к памяти, не принадлежащей массиву, что влечет за собой неопределенное поведение программы.
В этом легко убедиться, если записать предложение цикла следующим образом:
for (const int *p = a + 1; p != a + std::size( a ); ++p)
Однако в этом случае последний элемент массива не будет выведен на консоль.
О данном баге я также сообщил на сайте
www.stackoverflow.com.
Run-time error using a compound subscripting expression compiled by the MS VS22 C++ compiler