Вначале, %ebp указывает на некоторый адрес X в памяти. %esp ниже в стеке на адресе Y и указывает на последний элемент в нем. При входе в функцию, вы должны сохранить начало "текущего окружения", на которое указывает %ebp. Т.к. %ebp помещается в стек, %esp увеличивается на размер слова.
Вторая инструкция позволяет построить новое "окружение" для функции, %ebp теперь указывает на вершину стека. Поэтому %ebp и %esp указывают на одно и то же слово в памяти, которое содержит адрес предыдущего окружения.
Теперь должно быть выделено место в стеке для локальных переменных. Массив символов определен состоящим из 5 элементов и требует для размещения 5 байт (char занимает один байт). Однако стек работает только со словами и может выделять память только кратную слову(одно слово, 2 слова, 3 слова, ...). Чтобы сохранить 5 байт в случае четырехбайтного слова, вам нужно использовать 8 байт (что есть 2 слова). Серая часть может быть использована, даже если она не является частью строки. Целое k занимает 4 байта. Это место резервируется увелечением значения %esp на 0xc (шеснадцатиричное 12). Локальные переменные используют 8+4=12 байт (т.е. 3 слова).
Кроме самого механизма действия, важная вещь, которую надо здесь запомнить, - это расположение локальных переменных: локальные переменные имеют отрицательное смещение по отношению к %ebp. Инструкция i=0 в функции main() показывает это. Ассемблерный код (сравните ниже) использует косвенную адресацию для доступа к переменной i:
0x8048411 <main+25>: movl $0x0,0xfffffffc(%ebp)
Шеснадцатиричное 0xfffffffc представляет целое -4. Запись означает: поместить значение 0 в переменную, отстоящую на "-4 байта" по отношению к регистру %ebp. i - первая и единственная локальная переменная в функции main(), поэтому ее адрес - 4 байта (т.е. размер целого) "ниже" регистра %ebp.