Например, функции ввода/вывода в POSIX возвращают EINTR, если во время их работы в приложение пришел сигнал.
Сигнал вы можете перехватить, обработать и можно продолжать дальше. Но функция (например write) вернет EINTR. Если эту ситуацию не обрабатывать отдельно, то это может вызвать, например, завершение приложения (из-за ошибки ввода/вывода), тогда как приложение могло бы дальше работать.
В этом случае удобно вызов функции заключать в do while:
int ret;
do {
ret = write(...);
} while(ret == EINTR);
Это один из вариантов, когда надо выполнить действие, и по результатам действия будет понятно надо ли еще раз его запустить или уже достаточно.
В этот же цикл можно добавить и другой функционал.
В случае неблокируемых операций ввода/вывода операция может завершится не записав или не прочитав все что нужно или вообще вернуть EAGAIN, если ничего нет. Можно эти проверки добавить в этот же цикл do while вместе с проверкой на EINTR.
Вариантов использования, на самом деле много. Вы это увидите, когда столкнетесь с таким вариантом в своей практике.
Но самый популярный вариант у Си программистов, это использование в макросах:
#define MACRO(x) do { тут что-то делаем } while(0)
Этот фиктивный цикл служит двум целям:
1. внутри цикла можно объявлять локальные переменные, которые не будут видны вне цикла
2. При использовании макроса можно в конце ставить точку с запятой, не нарываясь на предупреждения компилятора:
MACRO(val);
. Компилятор в итоге уберет цикл, но все побочные эффекты сохранятся.
В плюсах этот вариант практически не используется, т.к. тут есть шаблоны и т.п. и от макросов в основном отказываются. Но в Си макросы вполне актуальный инструмент.
Но циклы for и while применяются чаще, чем do while.