Возьмите следующее:
int a(void) {
puts("a");
return 0;
}
int b(void) {
puts("b");
return 1;
}
int c(void) {
puts("c");
return 2;
}
int d(void) {
puts("d");
return 3;
}
Будет ли следующее поведение предсказуемым?
int arr[4][4][4][4];
arr[a()][b()][c()][d()] = 1;
Гарантируется ли печать в таком порядке:
a
b
c
d
Я знаю, что такие конструкции, как следующие, недействительны:
int i;
i = i++;
Это связано с тем, что = является непоследовательным оператором, поэтому не определено, вычисляется ли сначала i или i++. Доступ к одному объекту и его изменение перед другой точкой следования — это неопределенное поведение.
Иными словами, справедливо ли следующее:
int i = 0, arr[4][4][4][4];
arr[i++][i++][i++][i++] = 1;
Или он вызывает неопределенное поведение из-за непоследовательной модификации и доступа к i?
Согласно стандарту C, существует ли определенная точка последовательности между каждым последующим [] при индексировании многомерного массива?
Чтобы было ясно, ни один из этих примеров не имеет отношения к приоритету, размещению неявных скобок, порядку, в котором операторы работают со своими операндами. Вопрос касается последовательности, порядка, в котором оцениваются сами операнды.
Что касается того, что определяет порядок вызова функций: стандарт содержит правила, касающиеся порядка оценки, которые определяют это. Точно так же стандарт содержит правила, описывающие, где размещаются точки последовательности. Они связаны, но различны: одно не определяет другое — стандарт определяет их обоих.
@user16217248 user16217248 AFAIK, C не размещает точки последовательности между оценками отдельных индексов массива. Список здесь.
Связанный: В чем разница между приоритетом оператора и порядком оценки?
Не гарантируется, что индексы доступа к многомерному массиву будут оцениваться в каком-либо конкретном порядке. Демонстрация показывает вызываемые функции слева направо, но при выборе другого компилятора в Godbolt они оцениваются справа налево.
После дальнейшего изучения второй пример кода вызывает предупреждение с помощью Clang:
warning: multiple unsequenced modifications
to 'i' [-Wunsequenced]
arr[i++][i++][i++][i++] = 1;
^ ~~
Доступ к многомерному массиву можно разбить на серию (array)[index], где array — это более высокое измерение многомерного массива (arr само по себе является высшим измерением), а index — индексное выражение, такое как вызов функции или выражение i++.
Стандарт утверждает, что lhs[rhs] эквивалентен *((lhs)+(rhs)), поэтому неясно, оценивается ли сначала какой-либо заданный index или array, который он индексирует, поскольку оператор + не имеет последовательности. Во всех случаях, когда array не является самим arr, оценка array включает в себя оценку его index в еще более высоком измерении.
Следовательно, порядок, в котором оцениваются индексы доступа к многомерному массиву, не определен.
Это интересный момент: «... но выбор другого компилятора в Godbolt оценивает их справа налево». Меня интересовало определение предсказуемого поведения. Это предсказуемо на уровне компиляции или на уровне выполнения? т.е. изменится ли поведение после компиляции, скажем, во время выполнения? или быть последовательным?
Просто почитайте об этом, интересно. pvs-studio.com/ru/docs/warnings/v567
@Emile Стандарт C, насколько мне известно, не делает различий между непредсказуемым поведением во время компиляции, но постоянным во время выполнения или непредсказуемым периодом поведения. Хотя на практике некоторые конструкции, хотя и имеют неопределенное поведение, будут вести себя одинаково между выполнениями после компиляции, что касается стандарта C, неопределенное поведение является неопределенным поведением. Предложение соответствует линиям поведения, для которых предоставляются две или более возможности и не предъявляются дополнительные требования, которые выбираются в любое время.
@Emile Поведение конструкций, как указано в Стандарте, кажется либо переносимым (однозначное, четко определенное поведение), определенной реализацией (реализации могут выбирать, но должны документировать поведение и придерживаться его), неуказанным (две или более возможности) , никаких дополнительных требований) и undefined (носовые демоны).
@user16217248: обратите внимание, что стандарт распознает три ситуации, когда он характеризует программу как вызывающую неопределенное поведение (программа выполняет непереносимую конструкцию, которая может быть правильной, программа выполняет ошибочную конструкцию или правильная и переносимая программа получает ошибочные данные) , и хотя Стандарт отказывается от юрисдикции во всех трех ситуациях, это не предназначено для того, чтобы во всех трех случаях приглашать гнусавых демонов.
Что может быть примером третьего?
Будет ли следующее поведение предсказуемым? int arr[4][4][4][4]; arr[a()][b()][c()][d()] = 1;
Нет.
Хотя оценка элементов массива будет оцениваться слева направо, поскольку один является операндом для следующего, нет гарантии, что сами индексы массива будут оцениваться слева направо.
Чтобы быть более конкретным, arr[a()] оценивается до arr[a()][b()], которое оценивается до arr[a()][b()][c()], которое оценивается до arr[a()][b()][c()][d()]. Однако a(), b(), c() и d() можно оценивать в любом порядке.
Раздел 6.5p3 стандарта C относительно выражений гласит:
Группировка операторов и операндов указывается синтаксисом. За исключением случаев, указанных ниже, побочные эффекты и расчеты значений подвыражения не упорядочены
В разделах 6.5.2.1, касающихся индексации массива, не упоминается последовательность операндов E1[E2], хотя и утверждается, что предыдущее выражение точно эквивалентно (*((E1)+(E2))). Затем, глядя на раздел 6.5.3.2, касающийся оператора косвенности *, и раздел 6.5.6, касающийся аддитивного оператора +, ни один из них не упоминает о вычислении их операндов, которые каким-либо образом упорядочены. Таким образом, применяется 6.5p3, и функции a, b, c и d можно вызывать в любом порядке.
По тем же причинам это:
arr[i++][i++][i++][i++] = 1;
Запускает неопределенное поведение, поскольку оценка индексов массива не упорядочена по отношению друг к другу, и у вас есть несколько побочных эффектов для одного и того же объекта без точки последовательности.
Однако arr[a()][b()][c()][d()] может быть в порядке, если a(), b(), c() и d() не зависят друг от друга.
@Spencer В любом случае все в порядке, так как вызовы функций не будут чередоваться друг с другом. Таким образом, в этом случае нет неопределенного поведения, просто неопределенное поведение.
Зависит от того, что вы понимаете под ОК. Поскольку функции OP имеют общие побочные эффекты, они на самом деле не являются «независимыми». Я просто думаю, что добавление деталей в мой первый комментарий позволит избежать категорического «не делайте этого».
Учитывая конструкции
int x; // At file scope
... and then within some function
arr[f()][x] += 1;
arr[x][f()] += 2;
Для компилятора не было бы необычным обрабатывать каждую строку, выполняя вызов f() перед чтением x, независимо от того, был ли вызов функции первым или вторым индексом. Хотя фактическое добавление внутреннего индекса к указателю подмассива может быть невозможным до тех пор, пока не будет вычислен адрес подмассива, компилятор может вычислить любой или все индексы массива до начала работы по вычислению адреса.
В общем, доступ к элементам многомерного массива упорядочен.
Например, если у вас есть двумерный массив arr и вы обращаетесь к элементу arr[i][j], порядок оценки четко определен: сначала осуществляется доступ к arr[i] для извлечения одномерного массива, а затем осуществляется доступ к arr[i][j] для извлечения элемента в позиции [i][j] в этом одномерном массиве.
Точно так же, если у вас есть трехмерный массив arr и вы обращаетесь к элементу arr[i][j][k], порядок вычисления четко определен: сначала осуществляется доступ к arr[i] для получения двумерного массива. массив, затем доступ к arr[i][j] для извлечения одномерного массива, и, наконец, доступ к arr[i][j][k] для извлечения элемента в позиции [i][j][k] в этот одномерный массив.
Однако важно отметить, что порядок оценки доступа к многомерным массивам может зависеть от языка программирования или реализации. Кроме того, если к одному и тому же массиву одновременно обращаются несколько потоков, порядок оценки может быть непредсказуемым, поскольку операции могут чередоваться произвольным образом.
Хотя верно то, что arr[i] должно оцениваться перед arr[i][j], неверно, что i и сами j оцениваются в любом четко определенном порядке.
x[i]
определяется как(*((x)+(i)))
, поэтомуx[i][j]
становится(*((x[i])+(j)))
, которое становится(*(((*((x)+(i))))+(j)))
. В этом выражении нет точек следования, поэтому порядок вычисления не указан. (Если убрать лишние скобки, получится*(*(x+i)+j)
.)