session_start создает сессию. Что это значит? Это значит что оно создает место куда будет сохраняться инфа (файл по умолчанию) и присваивает этому месту какой-то рандомный идентификатор. Что бы сессия не потерялась пользователь должен отправлять его при каждом запросе. Так сложилось что это дело разруливают через куки.
Куки сетятся заголовком Set-Cookie, так что когда вы вызываете функцию session_start это равносильно отправке заголовка пользователю.
Вообще в HTTP запросах/ответах есть две части - заголовки и тело. Заголовки описывают параметры запроса/ответа, в частности что именно содержится в теле запроса/ответа и в случае запроса, что хочет получить пользователь/клиент.
Думаем дальше. для работы с заголовками ответов SAPI в PHP предоставляет вам функцию header. Упростим для себя жизнь и представим что session_start дергает внутри оную. Все что выходит через другие места в stdout (через echo или print например или просто какой-то символ перед <?php затесался) считается телом ответа. Так как должна соблюдаться последовательность действий при формировании HTTP ответа, то вы не можете менять заголовки как только хоть что-то, даже один байт, попал в буфер вывода.
Частично эту проблему решают функции управления буфером вывода. То есть мы можем сказать пыху что бы тот чуть подожал, а затем сделать flush буфера. Тогда можно в коде спокойно менять местами echo и header.
Вот... Помимо несоблюдения порядка взаимодействия с заголовками и выводом инфы частенько встречается такая штука как заголовки самих исходников (UTF BOM). Их умеет убирать любой нормальный редактор.
Так же рекомендуется не закрывать <?php тег так как после закрытого тега может затесаться лишний перевод строки и при инклудах это сыграет злую шутку.