Код в целом оптимальный, но первый цикл можно было скомпоновать чуть удачнее:
do {
if ((c = arr[i++]) != 0)
arr[cnt++] = c;
} while (++i < sizeof(arr));
Во-первых, do-while подходит больше, т.к. размер массива не может быть меньше 1. Бонусом вместо 3 джампов (1-й в самом начале цикла, после проверки условия выхода, 2-й в виде continue и 3-й в конце тела цикла while) получаем всего 2 (1-й в виде if и 2-й после проверки условия выхода из do-while).
Во-вторых, присваивание в переменную 'c' перемещено в выражение внутри if, чтобы вместо 2 обращений к 'c' (запись в 'c', затем оттуда же чтение) было всего 1 (сначала запись значения в 'c', а затем "повторное использование" того же значения в проверке на равенство нулю, вместо того чтобы заново считывать его из 'c').
(На самом деле даже это не самый удачный вариант цикла, просто те 2 оптимизационных приёма было проще всего объяснить.)