Внутри одной функции массив может храниться в одном или нескольких регистрах, лишь бы компилятор мог генерировать инструкции ЦПУ для манипулирования им, как того требует код. Стандарт на самом деле не определяет, что значит «быть» в регистре. Это частный вопрос между компилятором и отладчиком, и может быть тонкая грань между чем-то, что находится в регистре и полностью «оптимизируется».
В вашем примере параметр является указателем, а не массивом (см. Ответ dribeas). Поэтому было бы необычно, что массив, на который он указывает, мог бы быть регистром. «Основные» архитектуры, с которыми вы, вероятно, имеете дело, не позволяют указатель на регистр, поэтому даже если массив содержался в регистре в вызывающем коде, он должен был бы быть записан в память, чтобы получить указатель на это, чтобы перейти к вызываемому.
Если бы вызов функции был встроенным, то возможна лучшая оптимизация, как если бы вообще не было вызова.
Если вы оберните свой массив в структуру, тогда вы превратите его во что-то, что может быть передано по значению:
struct Foo {
char a[4];
};
void FooFunc(Foo f) {
// whatever
}
Теперь, функция принимает фактические данные массива в качестве своего параметра, поэтому есть еще один барьер для их хранения в регистре. Вопрос о том, действительно ли соглашение о вызовах реализации передает небольшие структуры в регистрах, - это другой вопрос. Я не знаю, какие соглашения о вызовах делают это, если таковые имеются.