ПрограммированиеВедущий C разработчик, Системный программист

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

Проходите собеседования с ИИ помощником Hintsage

Ответ

В языке C порядок вычисления аргументов функции не определён стандартом (до C99 включительно). Аргументы могут вычисляться слева направо, справа налево или в любом другом порядке (на усмотрение компилятора или архитектуры).

  • Все выражения/побочные эффекты в аргументах должны быть завершены до вызова функции, но нет гарантии, что они выполняются в каком-то определённом порядке.
  • Это значит, что использование переменных с изменением значения в нескольких аргументах — потенциально источник неопределённого поведения (undefined behavior) или просто различий между архитектурами.

Пример

void fn(int a, int b) { /* ... */ } int x = 1; fn(x++, x++); // порядок вычисления x++ и x++ не определён!

Вопрос с подвохом

"В какой последовательности вычисляются аргументы функции в C и можно ли на это полагаться при написании кода?"

Частая ошибка — считать, что аргументы вычисляются слева направо (по аналогии с выражениями). На практике каждый вызов функции компилируется на усмотрение компилятора (и платформы).

void foo(int a, int b, int c); int x = 1; foo(x++, x++, x++); // результат зависит от порядка вычисления аргументов

Настоящий ответ: полагаться нельзя — поведение не определено!

Примеры реальных ошибок из-за незнания тонкостей темы


История

В многоплатформенном продукте программист писал push(stack, stack->size++, data);. На большинстве платформ всё работало, но на одной размер стека увеличивался до передачи данных, на другой — после. Данные "терялись" или адресовались некорректно, ошибка проявлялась редко и была очень сложна для отладки.


История

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


История

В интерфейсе управления оборудованием функция инициализации передавала указатели и смещения с инкрементом внутри аргументов (типа init(ptr++, cnt++);). На одних процессорах железо инициализировалось правильно, а на других возникал сбой, причину долго искали в аппаратуре, хотя проблема была в некорректном коде C.